Creating an OS Deployment Summary Email with Orchestrator

System Center Orchestrator is very useful as complementary tool to OS Deployment.  I use it for several things during deployment including adding computers to SCCM collections, adding computers to AD groups, moving computer accounts into different AD OUs, adding a description to the computer account etc.  Previously, I had to use scripts to accomplish those things.  One other thing I also find useful is sending emails at different stages during the deployment, for example when it starts, if it fails or is cancelled and when it completes.  I like to send a deployment summary email at the beginning of the deployment that uses the task sequence variables to give me various data about the deployment.  It’s quite easy to set up, and in this post I’ll explain how to do it.  I’ll assume you’re familiar with creating basic Orchestrator runbooks.

Decide What to Include in Your Email

In my email, I include the values for some default task sequence variables and some additional ones that I’ve created and populated using the UDI Wizard.  Since only IT admins run our OS Deployments, we use the UDI wizard to get some information into the deployment.  I created a custom UDI wizard page that gets data for some custom TS variables, such as the architecture of the OS being deployed, whether the HDD will be encrypted, which disk partition is being installed to, etc.  Of course, you don’t need to use the UDI wizard, there are other ways to get custom variables into the deployment if you need to.

To get an export of all TS variables available to you during the deployment, use Daniel Oxley’s VBScript found here.

Create a Runbook

Create a new runbook and add an ‘Initialize Data’ activity from the Runbook Control group and a ‘Send Email’ activity from the Email group.  Both of these activities are native to Orchestrator and don’t require any additional Integration Packs. Create a simple link between the two.


Initialize Data

Double-click the ‘Initialize Data’ activity to edit it.  Click ‘Add’, and enter a TS variable name that you want to include.  It should match the TS variable name exactly.  Do that for all the variables you want to add.

Here’s a list of the ones I use:

HDDEncrypt (custom)
OSArch (custom)
OSDeployer (custom)
OSDWindowsPartition (custom)

You can see that some of the variables are custom ones, and the DaRT ones are only available if you have integrated DaRT in your boot image and have activated a remote connection (I do that automatically with a Prestart command in the boot image).

initSend Email

Next, double-click the ‘Send Email’ activity to edit it.  On the Connect page, enter the Sender email address and the smtp server.


On the Details page, enter a subject for your email.  Right-click in the subject field and choose ‘Expand’ so you can see all the text.  I’ve included the OSDComputerName and OSDStartTime variables in the subject by right-clicking, choosing Subscribe > Published Data and selecting the parameters from the Initialize Data activity.



Add a recipient email address.  Since I want to send the email to the IT admin who is deploying the machine, I use a custom variable ‘OSDeployer’ populated by the admin in the UDI wizard, adding it via the Published Data as before.  I also bcc myself 🙂


Next, enter the body of the email in the message field.  I use the Published Data again to add the parameters I want:

messageSave the runbook, then head over to your SCCM/MDT server.  Edit your Deployment task sequence and add an ‘Execute Runbook’ step from the menu.  Be sure to add it after a ‘Gather’ step and before a ‘Reboot Computer’ step, so all the variables are present in the TS environment.  Enter the name of your Orchestrator server and click Browse to choose the runbook.  As long as your parameters are named identically to the TS variables, you can choose the ‘Automatically provide runbook parameter values’ option.  Be sure to also check ‘Wait for the runbook to finish before continuing’, or the email won’t be sent.


The result

We get an email containing useful information about the deployment once it is kicked off, including a link to the DaRT Remote Session and the location of log files for that machine 🙂email
Orchestrator can be a great tool to help your OS deployments, and creativity is your main limitation!

Installing and Configuring WSUS with Powershell

In setting up our SCCM 2012 infrastructure, I decided to patch our OS deployments using WSUS instead of SCCM Software Updates.  Since we have multiple distribution points in different geographical areas, I decided to install a WSUS server in each location where we do deployments.  Granted, installing and configuring WSUS is not the most technically challenging thing in the world, but when you have to do it multiple times, it begs for automation!  So I fired up my trusty Powershell ISE to see what could be done.

I wrote this script for my own environment, but it should be flexible enough to be used by anyone.  It’s tested for use on Windows Server 2012 R2 and It’s designed to run in Powershell ISE, so it doesn’t take any parameters, just set the variables as required.  Also you can change any of the WSUS configuration, such as Products and Classifications, just edit the relevant section of the script.

Download from Technet Gallery.

What does the script do?

First, we install .Net Framework 3.5 if it isn’t already installed, this is a requirement for WSUS.  Next, we download and install Microsoft Report Viewer 2008 SP1, which is required for viewing WSUS reports.  If you chose the ‘SQLExpress’ installation, we download SQL Server 2012 Express SP1 with tools and run an unattended installation using default parameters.  Then we install WSUS and run the post-installation tasks with wsusutil.exe.

Now, we do a basic configuration, which is equivalent to running the WSUS Configuration Wizard.  We set the location to sync updates from, the update language/s, run a metadata sync to get available Products and Classifications, set which Products and Classifications we want to sync, and enable the automatic sync schedule.  Then we do a full sync.

Once the sync is completed, we decline certain updates that we don’t want, such as all ‘itanium’ updates, configure and enable the Default Automatic Approval Rule, then run it so the updates will start downloading.

Most of these activities are optional and are activated using variables which you must set before you run the script, so if you want to use WID, or an existing SQL instance you can.  You can skip the configuration entirely and do it manually, or just do the bare minimum, and of course you can customise the configuration in the script.

Step by Step Walkthrough

First, we set the variables, such as the WSUS installation type, the location for Updates, things to configure etc.

## Variables ##


# Do you want to install .NET FRAMEWORK 3.5? If true, provide a location for the Windows OS media in the next variable
    $DotNet = $True
# Location of Windows sxs for .Net Framework 3.5 installation
    $WindowsSXS = "D:\sources\sxs"
# Do you want to download and install MS Report Viewer 2008 SP1 (required for WSUS Reports)?
    $RepViewer = $True
# WSUS Installation Type.  Enter "WID" (for WIndows Internal Database), "SQLExpress" (to download and install a local SQLExpress), or "SQLRemote" (for an existing SQL Instance).
    $WSUSType = "SQLRemote"
# If using an existing SQL server, provide the Instance name below
    $SQLInstance = "MyServer\MyInstance"
# Location to store WSUS Updates (will be created if doesn't exist)
    $WSUSDir = "C:\WSUS_Updates"
# Temporary location for installation files (will be created if doesn't exist)
    $TempDir = "C:\temp"


# Do you want to configure WSUS (equivalent of WSUS Configuration Wizard, plus some additional options)?  If $false, no further variables apply.
# You can customise the configurations, such as Products and Classifications etc, in the "Begin Initial Configuration of WSUS" section of the script.
    $ConfigureWSUS = $True
# Do you want to decline some unwanted updates?
    $DeclineUpdates = $True
# Do you want to configure and enable the Default Approval Rule?
    $DefaultApproval = $True
# Do you want to run the Default Approval Rule after configuring?
    $RunDefaultRule = $False

We install .Net Framework 3.5 if required

# Install .Net Framework 3.5 from media
if($DotNet -eq $true)
write-host 'Installing .Net Framework 3.5'
Install-WindowsFeature -name NET-Framework-Core -Source $WindowsSXS

We install the Report Viewer from Microsoft for viewing WSUS reports. We start a bits job to download it, the we do a silent install.

# Download MS Report Viewer 2008 SP1 for WSUS reports

if ($RepViewer -eq $True)
write-host "Downloading Microsoft Report Viewer 2008 SP1...please wait"
$URL = ""
Start-BitsTransfer $URL $TempDir -RetryInterval 60 -RetryTimeout 180 -ErrorVariable err
if ($err)
write-host "Microsoft Report Viewer 2008 SP1 could not be downloaded!" -ForegroundColor Red
write-host 'Please download and install it manually to use WSUS Reports.' -ForegroundColor Red
write-host 'Continuing anyway...' -ForegroundColor Magenta

# Install MS Report Viewer 2008 SP1

write-host 'Installing Microsoft Report Viewer 2008 SP1...'
$setup=Start-Process "$TempDir\ReportViewer.exe" -verb RunAs -ArgumentList '/q' -Wait -PassThru
if ($setup.exitcode -eq 0)
write-host "Successfully installed"
write-host 'Microsoft Report Viewer 2008 SP1 did not install correctly.' -ForegroundColor Red
write-host 'Please download and install it manually to use WSUS Reports.' -ForegroundColor Red
write-host 'Continuing anyway...' -ForegroundColor Magenta

I prefer to use WSUS with a local SQL Express installation so I have some access to the database if I need to.  If chosen, we download and install SQL Server Express 2012 SP1 with admin tools using an unattended installation. We use the ‘ALLFEATURES_WITHDEFAULTS’ role, and add the local administrators group to the SQL sysadmin accounts.

# Download SQL 2012 Express SP1 with tools

if ($WSUSType -eq 'SQLExpress')
write-host "Downloading SQL 2012 Express SP1 with Tools...please wait"
Start-Sleep -Seconds 10 # wait 10 seconds in case of BITS overload error
$URL = ""
Start-BitsTransfer $URL $TempDir -RetryInterval 60 -RetryTimeout 180 -ErrorVariable err
if ($err)
write-host "Microsoft SQL 2012 Express SP1 could not be downloaded!  Please check internet availability." -ForegroundColor Red
write-host 'The script will stop now.' -ForegroundColor Red

# Install SQL 2012 Express with defaults

write-host 'Installing SQL Server 2012 SP1 Express with Tools...'

if ($setup.exitcode -eq 0)
write-host "Successfully installed"
write-host 'SQL Server 2012 SP1 Express did not install correctly.' -ForegroundColor Red
write-host 'Please check the Summary.txt log at C:\Program Files\Microsoft SQL Server\110\Setup Bootstrap\Log' -ForegroundColor Red
write-host 'The script will stop now.' -ForegroundColor Red

Next we install WSUS

# Install WSUS (WSUS Services, SQL Database, Management tools)

if ($WSUSType -eq 'WID')
write-host 'Installing WSUS for WID (Windows Internal Database)'
Install-WindowsFeature -Name UpdateServices -IncludeManagementTools
if ($WSUSType -eq 'SQLExpress' -Or $WSUSType -eq 'SQLRemote')
write-host 'Installing WSUS for SQL Database'
Install-WindowsFeature -Name UpdateServices-Services,UpdateServices-DB -IncludeManagementTools

Then we run the post-install configuration tasks using the wsusutil.exe

# Run WSUS Post-Configuration

if ($WSUSType -eq 'WID')
sl "C:\Program Files\Update Services\Tools"
.\wsusutil.exe postinstall CONTENT_DIR=$WSUSDir
if ($WSUSType -eq 'SQLExpress')
sl "C:\Program Files\Update Services\Tools"
if ($WSUSType -eq 'SQLRemote')
sl "C:\Program Files\Update Services\Tools"
.\wsusutil.exe postinstall SQL_INSTANCE_NAME=$SQLInstance CONTENT_DIR=$WSUSDir

Now we begin to configure WSUS. We connect to the WSUS server and get the configuration. We tell it to sync from Microsoft Update, then set the updates language to English.

# Get WSUS Server Object
$wsus = Get-WSUSServer

# Connect to WSUS server configuration
$wsusConfig = $wsus.GetConfiguration()

# Set to download updates from Microsoft Updates
Set-WsusServerSynchronization –SyncFromMU

# Set Update Languages to English and save configuration settings
$wsusConfig.AllUpdateLanguagesEnabled = $false

We do an initial sync to get the available products and categories from Microsoft Update

# Get WSUS Subscription and perform initial synchronization to get latest categories
$subscription = $wsus.GetSubscription()
write-host 'Beginning first WSUS Sync to get available Products etc' -ForegroundColor Magenta
write-host 'Will take some time to complete'
While ($subscription.GetSynchronizationStatus() -ne 'NotProcessing') {
    Write-Host "." -NoNewline
    Start-Sleep -Seconds 5
write-host ' '
Write-Host "Sync is done." -ForegroundColor Green

We tell WSUS which Products we want to sync. It’s very important to get these right, otherwise you will download a lot of updates that you don’t need and fill up your disk space! Obviously you’ll want to customise these for your environment.

# Configure the Platforms that we want WSUS to receive updates
write-host 'Setting WSUS Products'
Get-WsusProduct | where-Object {
    $_.Product.Title -in (
    'Report Viewer 2005',
    'Report Viewer 2008',
    'Report Viewer 2010',
    'Visual Studio 2005',
    'Visual Studio 2008',
    'Visual Studio 2010 Tools for Office Runtime',
    'Visual Studio 2010',
    'Visual Studio 2012',
    'Visual Studio 2013',
    'Microsoft Lync 2010',
    'Microsoft SQL Server 2008 R2 - PowerPivot for Microsoft Excel 2010',
    'Dictionary Updates for Microsoft IMEs',
    'New Dictionaries for Microsoft IMEs',
    'Office 2003',
    'Office 2010',
    'Office 2013',
    'System Center 2012 - Orchestrator',
    'Windows 7',
    'Windows 8.1 Drivers',
    'Windows 8.1 Dynamic Update',
    'Windows 8',
    'Windows Dictionary Updates',
    'Windows Server 2008 R2',
    'Windows Server 2008',
    'Windows Server 2012 R2',
    'Windows Server 2012',
    'Windows XP 64-Bit Edition Version 2003',
    'Windows XP x64 Edition',
    'Windows XP')
} | Set-WsusProduct

We do the same for the Update Classifications

# Configure the Classifications
write-host 'Setting WSUS Classifications'
Get-WsusClassification | Where-Object {
    $_.Classification.Title -in (
    'Critical Updates',
    'Definition Updates',
    'Feature Packs',
    'Security Updates',
    'Service Packs',
    'Update Rollups',
} | Set-WsusClassification

I guess it’s a bug, but it seems WSUS sometimes enables the entire parent Product when adding them by script this way, so we pause the script and prompt to check in the WSUS console that the correct Products are selected before continuing.

# Prompt to check products are set correctly
write-host 'Before continuing, please open the WSUS Console, cancel the WSUS Configuration Wizard,' - -ForegroundColor Red
write-host 'Go to Options > Products and Classifications, and check that the Products are set correctly.' - -ForegroundColor Red
write-host 'Pausing script' -ForegroundColor Yellow
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Click OK to continue.", 0, "Script Paused", 0) # Using Pop-up in case script is running in ISE

We set the automatic sync schedule to once per day at midnight, then start the first full synchronisation.

# Configure Synchronizations
write-host 'Enabling WSUS Automatic Synchronisation'

# Set synchronization scheduled for midnight each night
$subscription.SynchronizeAutomaticallyTimeOfDay= (New-TimeSpan -Hours 0)

# Kick off a synchronization

We monitor the progress of the sync in the Powershell console as it can take some time.

# Monitor Progress of Synchronisation

write-host 'Starting WSUS Sync, will take some time' -ForegroundColor Magenta
Start-Sleep -Seconds 60 # Wait for sync to start before monitoring
while ($subscription.GetSynchronizationProgress().ProcessedItems -ne $subscription.GetSynchronizationProgress().TotalItems) {
    Write-Progress -PercentComplete (
    ) -Activity "WSUS Sync Progress"
Write-Host "Sync is done." -ForegroundColor Green

After the sync is complete, we decline some updates that we don’t want. In my example, we are declining IE10 and the Microsoft Browser Choice EU updates, which we don’t want (I used the KB article number in the ‘TextIncludes’ parameter to find them), then we decline all ‘itanium’ updates because we don’t have any itanium servers. Do you?

# Decline Unwanted Updates

if ($DeclineUpdates -eq $True)
write-host 'Declining Unwanted Updates'
$approveState = 'Microsoft.UpdateServices.Administration.ApprovedStates' -as [type]

# Declining All Internet Explorer 10
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{
    TextIncludes = '2718695'
    ApprovedStates = $approveState::Any
$wsus.GetUpdates($updateScope) | ForEach {
    Write-Verbose ("Declining {0}" -f $_.Title) -Verbose

# Declining Microsoft Browser Choice EU
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{
    TextIncludes = '976002'
    ApprovedStates = $approveState::Any
$wsus.GetUpdates($updateScope) | ForEach {
    Write-Verbose ("Declining {0}" -f $_.Title) -Verbose

# Declining all Itanium Update
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{
    TextIncludes = 'itanium'
    ApprovedStates = $approveState::Any
$wsus.GetUpdates($updateScope) | ForEach {
    Write-Verbose ("Declining {0}" -f $_.Title) -Verbose

Then we enable the Default Automatic Approval Rule and configure it with the classifications we want.

# Configure Default Approval Rule

if ($DefaultApproval -eq $True)
write-host 'Configuring default automatic approval rule'
$rule = $wsus.GetInstallApprovalRules() | Where {
    $_.Name -eq "Default Automatic Approval Rule"}
$class = $wsus.GetUpdateClassifications() | ? {$_.Title -In (
    'Critical Updates',
    'Definition Updates',
    'Feature Packs',
    'Security Updates',
    'Service Packs',
    'Update Rollups',
$class_coll = New-Object Microsoft.UpdateServices.Administration.UpdateClassificationCollection
$rule.Enabled = $True

Finally we run the rule which will approve the updates and begin the file downloads. However, in my testing this always errors with a timeout when activated through Powershell, so I put it in a try-catch-finally block to finish the script successfully. Even if it errors, the rule is actually run as you will be able to see from the WSUS console.

# Run Default Approval Rule

if ($RunDefaultRule -eq $True)
write-host 'Running Default Approval Rule'
write-host ' >This step may timeout, but the rule will be applied and the script will continue' -ForegroundColor Yellow
try {
$Apply = $rule.ApplyRule()
catch {
write-warning $_
Finally {
# Cleaning Up TempDir

write-host 'Cleaning temp directory'
if (Test-Path $TempDir\ReportViewer.exe)
{Remove-Item $TempDir\ReportViewer.exe -Force}
if (Test-Path $TempDir\SQLEXPRWT_x64_ENU.exe)
{Remove-Item $TempDir\SQLEXPRWT_x64_ENU.exe -Force}
If ($Tempfolder -eq "No")
{Remove-Item $TempDir -Force}

write-host 'WSUS log files can be found here: %ProgramFiles%\Update Services\LogFiles'
write-host 'Done!' -foregroundcolor Green

Monitoring the Update File Downloads

After the Default Approval Rule has been run, you can monitor the ‘Download Status’ of the update files in the WSUS console.  But since it can take a long time, I wrote a little script that will monitor the downloads and email me once they have finished.  It must be run as administrator on the WSUS server.

$Computername = $env:COMPUTERNAME
$ToEmail = ""
$FromEmail = "WSUS.on.$"
$smtpServer = "mysmtpServer"
# Polling frequency in seconds
$Seconds = "320"

Write-host 'Monitoring WSUS Update File Downloads...'
write-host 'Will send email when completed.'
[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | out-null
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer();
$updateScope = new-object Microsoft.UpdateServices.Administration.UpdateScope;
$updateScope.updateApprovalActions = [Microsoft.UpdateServices.Administration.UpdateApprovalActions]::Install
while (($wsus.GetUpdates($updateScope) | Where {$_.State -eq "NotReady"}).Count -ne 0) {
Start-Sleep -Seconds $Seconds
send-mailmessage -To $ToEmail -From $FromEmail -Subject "WSUS Update File Download Completed on $ComputerName" -body "Download of Update Files on $Computername has completed!" -smtpServer $smtpServer

Email Notifications

Finally, if you configure E-mail Notifications in WSUS, you may hit the lovely 5.7.1 error from Exchange:

Mailbox unavailable. The server response was: 5.7.1 Client does not have permissions to send as this sender 

This is because it tries to authenticate with its computer account.  So you have to create a new Receive Connector in Exchange to allow relaying from anonymous users with TLS-authentication to work around the problem.

You can run a command like the following to create it:

New-ReceiveConnector -Name "WSUS Relay" -Bindings -RemoteIPRanges 10.x.x.1.,10.x.x.2 -AuthMechanism Tls -Enabled
 $true -PermissionGroups AnonymousUsers -Server MyEdgeServer

Incidentally, you can’t really configure E-mail Notifications with Powershell as you must set the recipient email address for it to work, and this is a read-only property that Powershell can’t change, so better to do it manually.

That’s it!  Feel free to suggest some improvements, or take the code and make something better yourself!

Most of the WSUS code I learned from these great resources, especially the work of Boe Prox

An Alternative to Application Supersedence in SCCM

For some software applications, it’s inevitable that you’ll need to deploy a new version now and then.  For example, you deploy an application, then a new version gets released and you want to upgrade all your clients.  Application supersedence in SCCM2012 is a useful way to deploy a new version of an application – you create the new application and specify supercedence of the old version.  You can also uninstall the old version if required.  But for some simple applications it seems overkill to have to create a new application with every release, especially as you have to create deployment types, add deployments, maybe update your task sequences to include the new application, retire the old application etc.

Is there a better way?  Well, for apps that don’t require you to uninstall the old version first, I think there is.  Simply add new deployment types to your existing application. Create a new deployment type with the new version number in the existing application.  Then create a ‘dummy’ requirement rule on the old deployment type to something it’s certain to fail during evaluation. This will effectively disable it, yet keep it should you need it in future.  And then increase the priority of the new deployment type to 1 so it will evaluate and install first.

An example with Adobe Flash (ok, I’m not using 3rd Party Update management yet!)


A ‘dummy’ requirement rule for the previous version, eg a WMI query for the computer manufacturer that will fail the evaluation:


There are several benefits:

  • You can keep the previous application versions (deployment types) for as long as you want, should you need them
  • You don’t need to create new applications for every new version
  • You don’t need to update your applications in your task sequences
  • You don’t even need to distribute the new content manually, as it will get distributed automatically after you created the new deployment type
  • You don’t need to create a new deployment, as it will use the existing application deployment
  • If you have a ‘required’ deployment, then clients will update themselves with the new version after the next Application Deployment Evaluation cycle
  • You can name your Applications more generically, eg ‘My Application’, instead of ‘My Application V1’, ‘My Application v2’ etc
  • It can save a lot of time if you update applications regularly

You might, of course, want to update the schedule on the deployment until after the new content has been distributed, or to control the deployment time.

For OSD applications which can only have one deployment type, you’ll need to update the existing deployment type instead.

Granted, it won’t work in every scenario, especially if you need to uninstall the old version, although you could run an ‘uninstall’ deployment first, then schedule an ‘install’ deployment after.  But for simple applications at least, it seems to me a better way to manage application version updates.