Translating Error Codes for Windows and Configuration Manager

As a Windows and Configuration Manager administrator, I often come across error codes that need translating into their more friendly descriptions.  In Configuration Manager, sometimes these codes are translated for you in the log files, reports and the ConfigMgr console, but sometimes they are not.  Sometimes they will be in decimal format, and sometimes hexadecimal.  For Windows error codes, there are a number of methods to return the friendly descriptions, for example the “net helpmsg”:

Capture

But it can only handle decimal codes:

Capture1

In PowerShell, there is the .Net namespace ComponentModel.Win32Exception, which can handle both decimal and hex:

Capture3

Common Windows error codes are also documented in MSDN:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/cc231199.aspx

However, for error codes that are specific to Configuration Manager, you can use the handy CMTRACE utility in the Configuration Manager toolkit, which has an error lookup.  This returns error descriptions for both Windows and Configuration Manager, supports decimal and hex, and supports error codes from more sources too, including WMI and Windows Update Agent:

Capture2

Capture4

Capture5

But if you are scripting and want to translate an error code, how can you do that?  Well there is a handy little dll file called SrsResources.dll that comes with the installation of the Configuration Manager Console, and can be found here: %ProgramFiles(x86)%\Microsoft Configuration Manager\AdminConsole\bin\SrsResources.dll.  Using this dll, we can translate error codes for Windows, Configuration Manager, WMI etc, and even translate status message IDs.  It will call other dll files when it needs to, to find the error string.

Using PowerShell, we can create the following simple function which will use the SrsResources.dll to translate a decimal or hex error code for us:


function Get-CMErrorMessage {
[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [int64]$ErrorCode
        )

[void][System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\SrsResources.dll")
[SrsResources.Localization]::GetErrorMessage($ErrorCode,"en-US")
}

Capture6

To take it further, we can export a list of error codes, for example here we will use the same function to enumerate all decimal codes between 0 and 50, and also output the equivalent hex codes:


$errorcodes = @()
$i = -1
Do
    {
        $i ++
        $description = Get-CMErrorMessage -ErrorCode $i
        if ($description -notlike "Unknown Error*")
            {
                $hex = '{0:x}' -f $i
                $errorcode = New-Object psobject
                Add-Member -InputObject $errorcode -MemberType NoteProperty -Name DecimalErrorCode -Value $i
                Add-Member -InputObject $errorcode -MemberType NoteProperty -Name HexErrorCode -Value ("0x" + $hex)
                Add-Member -InputObject $errorcode -MemberType NoteProperty -Name ErrorDescription -Value $description
                $errorcodes += $errorcode
            }

    }
Until ($i -eq 50)
$errorcodes | ft -AutoSize

Capture7Pretty cool 🙂  Using this SrsResources.dll creates a log file in your %TEMP% directory called SCCMReporting.log, and this log quickly increases in size, so if you use it a lot check the size of this log file from time to time.  The logging can be useful for identifying which dll was used to find the error string.

To convert between decimal and hexadecimal and vice-versa, we can use this simple function. With PowerShell, you can convert to decimal natively in the console just by entering the hexadecimal code,  but using this function allows us to convert both ways, and is more useful for scripts.


function Convert-ErrorCode {
[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$True,ParameterSetName='Decimal')]
            [int64]$DecimalErrorCode,
        [Parameter(Mandatory=$True,ParameterSetName='Hex')]
            $HexErrorCode
        )
if ($DecimalErrorCode)
    {
        $hex = '{0:x}' -f $DecimalErrorCode
        $hex = "0x" + $hex
        $hex
    }

if ($HexErrorCode)
    {
        $DecErrorCode = $HexErrorCode.ToString()
        $DecErrorCode
    }
}

Capture8Finally, wrapping all this together, here is a script that uses both functions we have created earlier, and will return all the machines that are in an error state for a ConfigMgr application deployment, with the error code and description.  Because we filter using the current application revision, this actually returns more accurate results than the ConfigMgr console > Deployments node, as that data will include previous application revisions where no data is available for the current revision, which produces misleading results.

First, we query WMI on the ConfigMgr site server for the list of applications and choose the one we want:

Capture

Then we query for the deployments and deployment types for that application, and choose the one we want.  The numbers of errors is returned, but as previously mentioned, this may not be completely accurate at this stage.

Capture2Then we return the results translating the error codes into their descriptions.

Capture3

Cool 🙂

Note that WMI stores the error codes as unsigned integers, but the ConfigMgr console displays errors as signed integers, so we do a conversion and include both in our results.

In the next blog, I describe how to create a SQL database of these error codes for easy referencing in SQL queries: Create a database of error codes and descriptions for Windows and Configmgr

Here’s the complete script:


<#

.SYNOPSIS
    Returns the error code and error descriptions for all computers in an error state for an application deployment

.DESCRIPTION
    This script asks you to choose a ConfigrMgr application, then choose a deployment / deployment type for that application, then returns all the computers that are in an error state for that
    deployment, with the error code and error description.
    Requires to be run on a computer with the ConfigMgr console installed, and the path to the SrsResources.dll needs to be specified in the "Get-CMErrorMessage" function.  You may also
    need to change the localization in this function to your region, eg "en-US".

.PARAMETER SiteServer
    The name of the ConfigMgr Site server

.PARAMETER SiteCode
    The ConfigMgr Site Code

.NOTES
    Script name: Get-CMAppDeploymentErrors.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-06-17
    Link:        https://smsagent.wordpress.com

#>

[CmdletBinding(SupportsShouldProcess=$True)]
    param
        (
        [Parameter(Mandatory=$False)]
            [string]$SiteServer="sccmserver-01",
        [Parameter(Mandatory=$False)]
            [string]$SiteCode="ABC"
        )

function Get-CMErrorMessage {
[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [int64]$ErrorCode
        )

[void][System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\SrsResources.dll")
[SrsResources.Localization]::GetErrorMessage($ErrorCode,"en-US")
}

function Convert-ErrorCode {
[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$True,ParameterSetName='Decimal')]
            [int64]$DecimalErrorCode,
        [Parameter(Mandatory=$True,ParameterSetName='Hex')]
            $HexErrorCode
        )
if ($DecimalErrorCode)
    {
        $hex = '{0:x}' -f $DecimalErrorCode
        $hex = "0x" + $hex
        $hex
    }

if ($HexErrorCode)
    {
        $DecErrorCode = $HexErrorCode.ToString()
        $DecErrorCode
    }
}

# Get Application
$App = Get-WmiObject -ComputerName $SiteServer -Namespace ROOT\sms\Site_$SiteCode -Class SMS_ApplicationLatest |
    Sort LocalizedDisplayName |
    Select LocalizedDisplayName,SDMPackageVersion,ModelName |
    Out-GridView -Title "Choose an Application" -OutputMode Single

# Get Deployment Types and Deployments for Application
$DT = Get-WmiObject -ComputerName $SiteServer -Namespace ROOT\sms\Site_$SiteCode -query "Select * from SMS_AppDTDeploymentSummary where AppModelName = '$($App.ModelName)'" |
    Select Description,CollectionName,CollectionID,NumberErrors,AssignmentID |
    Out-GridView -Title "Choose a Deployment / Deployment Type" -OutputMode Single

# Get Errors
$Errors = Get-WmiObject -ComputerName $SiteServer -Namespace ROOT\sms\Site_$SiteCode -query "Select * from SMS_AppDeploymentErrorAssetDetails where AssignmentID = '$($DT.AssignmentID)' and DTName = '$($DT.Description)' and Revision = '$($App.SDMPackageVersion)' and Errorcode <> 0" |
    Sort Machinename |
    Select MachineName,Username,Starttime,Errorcode

if ($Errors -ne $null)
{
    # Create new object with error descriptions in
    $AllErrors = @()
    foreach ($item in $Errors)
        {
            $errordescription = Get-CMErrorMessage -ErrorCode $item.Errorcode
            $hex = Convert-ErrorCode -DecimalErrorCode $item.Errorcode
            $int = [int]$hex
            $obj = New-Object psobject
            Add-Member -InputObject $obj -MemberType NoteProperty -Name ComputerName -Value $item.MachineName
            Add-Member -InputObject $obj -MemberType NoteProperty -Name UserName -Value $item.Username
            Add-Member -InputObject $obj -MemberType NoteProperty -Name StartTime -Value $([management.managementDateTimeConverter]::ToDateTime($item.Starttime))
            Add-Member -InputObject $obj -MemberType NoteProperty -Name UnsignedIntErrorCode -Value $item.Errorcode
            Add-Member -InputObject $obj -MemberType NoteProperty -Name SignedIntErrorCode -Value $int
            Add-Member -InputObject $obj -MemberType NoteProperty -Name HexErrorCode -Value $hex
            Add-Member -InputObject $obj -MemberType NoteProperty -Name ErrorDescription -Value $errordescription
            $AllErrors += $obj
        }
    # Return results
    write-host "Application: $($App.LocalizedDisplayName)"
    write-host "DeploymentType: $($DT.Description)"
    write-host "TargetedCollection: $($DT.CollectionName)"
    $AllErrors | ft -AutoSize
}
Else {Write-host "No results returned."}

 

Checking for New Versions of Common Software with Powershell

In any enterprise environment there will be some common software that you will install on all your computers, and some of these are frequently updated to new versions, such as Adobe Flash Player, or the Java Runtime for example.  So I wrote a little script that runs as a scheduled task every day and checks the internet for the current version of some common software, and sends me an email when a new version is released.

It works by checking an internet URL that contains the current version number of that software, then finds the HTML element in the page that contains that number, and extracts just the number.  Then it compares it with a local file that contains the current (or previous) version number, and if the version number has changed, it will update the local file with the new number and send me an email notification.

Currently it will work for the following software, but can be customized for others.

Adobe Flash Player
Adobe Acrobat Reader DC
Java Runtime
Notepad++
Paint.net
PDFCreator

Create a Local “Software Versions” file

Create a new text file exactly as in the screenshot below, and save it locally, eg “SoftwareVersions.txt”

Capture

Edit the Script

Save and edit the PowerShell script below, entering defaults for the email parameters and the location of the SoftwareVersions.txt file.  You can also enter these parameters when running the script.

Run the Script

The first time you run the script it will populate the text file with the current version numbers of each software, and send you an email for each.  The next time it runs, it will check the version number found online with the version number in the file, and only send an email if something is different.

Capture2

 

Capture3

Schedule the Script

Use a Windows Scheduled task to schedule the script and perform regular checks of the current software version.

Adding New Software

You can add new software by copying and pasting a new section of the script, finding a URL that contains the version number, finding the HTML element in the page that contains the version number, then using filters and text manipulation to get just the number itself from the web page.

You also need to add a line to your SoftwareVersions.txt file, and modify the code that uses it to search and update that line of the text file.

For example, once you have your URL, you can output all the HTML elements in the page to the PowerShell GridView, then use the criteria filters to find the element you want:


$URI = "https://get.adobe.com/uk/reader/"
$HTML = Invoke-WebRequest -Uri $URI
$HTML.AllElements | Out-Gridview

Capture4Now I use the following code to filter the “innerHTML” element using the filter “Version ..*”, which gives me the string “Version 2015.007.20033”, and using the “Split” method, I trim off the “Version ” part leaving just the number: “2015.007.20033”.


$NewReaderVersion = (($HTML.AllElements | where {$_.innerHTML -like "Version *.*.*"}).innerHTML).Split(" ")[1]

Now I get the content of the SoftwareVersions.txt file, and find the string, or the line, that contains the name of the software.  Using the Substring method, I remove the first 25 characters of the line, to leave me with only the version number.


$CurrentReaderVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Adobe Acrobat Reader DC").ToString()).Substring(25)

Next I can compare the two version numbers:


If ($NewReaderVersion -ne $CurrentReaderVersion)

If they are different, I send myself an email, and I also put the new version into the text file, which becomes the reference for the next check.


$Content = Get-Content $SoftwareVersionsFile
$NewContent = $Content.Replace("Adobe Acrobat Reader DC: $CurrentReaderVersion","Adobe Acrobat Reader DC: $NewReaderVersion")
$NewContent | Out-File $SoftwareVersionsFile -Force

Simples 🙂

The Script

<#

.SYNOPSIS
    Checks for the current version of common software and sends an email notification if the version has been updated

.DESCRIPTION
    This script checks for the current version of some common software from their internet URLs.  It then checks a local file for the stored software version.  If the two don't match,
    an email will be sent notifying of the new version number.  The stored version number will then be updated for future version checking.
    Currently software list:
    Adobe Flash Player
    Adobe Acrobat Reader DC
    Java Runtime
    Notepad++
    Paint.net
    PDFCreator

.PARAMETER To
    The "To" email address for notifications

.PARAMETER From
    The "From" email address for notifications

.PARAMETER Smtpserver
    The smtpserver name for email notifications

.PARAMETER SoftwareVersionsFile
    The location of the file used to store the software versions

.EXAMPLE
    Check-SoftwareVersions.ps1
    Checks the internet URLs of common software for the current version number and sends an email if a new version has been released.

.NOTES
    Script name: Check-SoftwareVersions.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-06-11
    Link:        https://smsagent.wordpress.com

#>


[CmdletBinding(SupportsShouldProcess=$True)]
    param
        (
        [Parameter(Mandatory=$False, HelpMessage="The 'to' email address")]
            [string]$To="bill.gates@contoso.com",
        [Parameter(Mandatory=$False, HelpMessage="The 'from' email address")]
            [string]$From="PowerShell@contoso.com",
        [Parameter(Mandatory=$False, HelpMessage="The 'from' email address")]
            [string]$SmtpServer="myexchangebox",
        [parameter(Mandatory=$False, HelpMessage="The location of the software versions file")]
            [string]$SoftwareVersionsFile="C:\Scripts\temp\SoftwareVersions.txt"
        )


$EmailParams = @{
    To = $To
    From = $From
    Smtpserver = $SmtpServer
    }

# Note: to find the element that contains the version number, output all elements to gridview and search with the filter, eg:
# $URI = "https://get.adobe.com/uk/reader/"
# $HTML = Invoke-WebRequest -Uri $URI
# $HTML.AllElements | Out-Gridview


######################
# Adobe Flash Player #
######################

Write-Verbose "Checking Adobe Flash Player"
$URI = "http://www.adobe.com/uk/products/flashplayer/distribution3.html"
$HTML = Invoke-WebRequest -Uri $URI
$NewFlashVersion = (($HTML.AllElements | where {$_.innerHTML -like "Flash Player*Win*"}).innerHTML).Split(" ")[2]
Write-Verbose "Found version: $NewFlashVersion"

$CurrentFlashVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Adobe Flash Player").ToString()).substring(20)
Write-Verbose "Stored version: $CurrentFlashVersion"

If ($NewFlashVersion -ne $CurrentFlashVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "Adobe Flash Update" -Body "Adobe Flash Player has been updated from $CurrentFlashVersion to $NewFlashVersion"
        write-verbose "Setting new stored version number for Adobe Flash Player"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("Adobe Flash Player: $CurrentFlashVersion","Adobe Flash Player: $NewFlashVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }


###########################
# Adobe Acrobat Reader DC #
###########################

Write-Verbose "Checking Adobe Acrobet Reader DC"
$URI = "https://get.adobe.com/uk/reader/"
$HTML = Invoke-WebRequest -Uri $URI
$NewReaderVersion = (($HTML.AllElements | where {$_.innerHTML -like "Version *.*.*"}).innerHTML).Split(" ")[1]
Write-Verbose "Found version: $NewReaderVersion"

$CurrentReaderVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Adobe Acrobat Reader DC").ToString()).Substring(25)
Write-Verbose "Stored version: $CurrentReaderVersion"

If ($NewReaderVersion -ne $CurrentReaderVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "Adobe Acrobat Reader Update" -Body "Adobe Acrobat Reader DC has been updated from $CurrentReaderVersion to $NewReaderVersion"
        write-verbose "Setting new stored version number for Adobe Acrobat Reader DC"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("Adobe Acrobat Reader DC: $CurrentReaderVersion","Adobe Acrobat Reader DC: $NewReaderVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }


################
# Java Runtime #
################

Write-Verbose "Checking Java Runtime"
$URI = "http://www.java.com/en/download/windows_offline.jsp"
$HTML = Invoke-WebRequest -Uri $URI
$NewJavaVersion = (($HTML.AllElements | where {$_.innerHTML -like "Recommended Version * Update *"}).innerHTML).Substring(20).Split("(")[0]
Write-Verbose "Found version: $NewJavaVersion"

$CurrentJavaVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Java Runtime").ToString()).Substring(14)
Write-Verbose "Stored version: $CurrentJavaVersion"

If ($NewJavaVersion -ne $CurrentJavaVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "Java Runtime Update" -Body "Java Runtime has been updated from $CurrentJavaVersion to $NewJavaVersion"
        write-verbose "Setting new stored version number for Java Runtime"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("Java Runtime: $CurrentJavaVersion","Java Runtime: $NewJavaVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }


##############
# Notepad ++ #
##############

Write-Verbose "Checking Notepad++"
$URI = "http://notepad-plus-plus.org/"
$HTML = Invoke-WebRequest -Uri $URI
$NewNotepadVersion = (($HTML.AllElements | where {$_.outerText -like "Download*" -and $_.tagName -eq "P"}).innerText).Split(":")[1].Substring(1)
Write-Verbose "Found version: $NewNotepadVersion"

$CurrentNotepadVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Notepad\+\+").ToString()).Substring(11)
Write-Verbose "Stored version: $CurrentNotepadVersion"

If ($NewNotepadVersion -ne $CurrentNotepadVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "Notepad++ Update" -Body "Notepad++ has been updated from $CurrentNotepadVersion to $NewNotepadVersion"
        write-verbose "Setting new stored version number for Notepad++"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("Notepad++: $CurrentNotepadVersion","Notepad++: $NewNotepadVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }


##############
# Paint.net  #
##############

Write-Verbose "Checking Paint.net"
$URI = "http://www.getpaint.net/index.html"
$HTML = Invoke-WebRequest -Uri $URI
$NewPaintVersion = (($HTML.AllElements | where {$_.innerHTML -clike "paint.net*.*.*"}).innerHTML).Substring(10)
Write-Verbose "Found version: $NewPaintVersion"

$CurrentPaintVersion = ((Get-Content $SoftwareVersionsFile | Select-string "Paint.net").ToString()).Substring(11)
Write-Verbose "Stored version: $CurrentPaintVersion"

If ($NewPaintVersion -ne $CurrentPaintVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "Paint.Net Update" -Body "Paint.Net has been updated from $CurrentPaintVersion to $NewPaintVersion"
        write-verbose "Setting new stored version number for Paint.net"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("Paint.net: $CurrentPaintVersion","Paint.net: $NewPaintVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }


##############
# PDFCreator #
##############

Write-Verbose "Checking PDFCreator"
$URI = "http://www.pdfforge.org/blog"
$HTML = Invoke-WebRequest -Uri $URI
$NewPDFCreatorVersion = ($HTML.AllElements | where {($_.innerHTML -eq $_.innerText) -and $_.tagName -eq "A" -and $_.innerHTML -like "PDFCreator*"})[0].innerHTML.Split(" ")[1]
Write-Verbose "Found version: $NewPDFCreatorVersion"

$CurrentPDFCreatorVersion = ((Get-Content $SoftwareVersionsFile | Select-string "PDFCreator").ToString()).Substring(12)
Write-Verbose "Stored version: $NewPDFCreatorVersion"

If ($NewPDFCreatorVersion -ne $CurrentPDFCreatorVersion)
    {
        Write-Verbose "Sending email"
        Send-MailMessage @EmailParams -Subject "PDFCreator Update" -Body "PDFCreator has been updated from $CurrentPDFCreatorVersion to $NewPDFCreatorVersion"
        write-verbose "Setting new stored version number for PDFCreator"
        $Content = Get-Content $SoftwareVersionsFile
        $NewContent = $Content.Replace("PDFCreator: $CurrentPDFCreatorVersion","PDFCreator: $NewPDFCreatorVersion")
        $NewContent | Out-File $SoftwareVersionsFile -Force
    }

Setting the Default Windows Wallpaper during OS Deployment

Note: For a Windows 10 Version, see this blog instead: https://smsagent.wordpress.com/2017/07/06/setting-the-default-wallpaper-for-windows-10-during-configmgr-osd/

Recently I was given an interesting task – set the default wallpaper on new computer builds with ConfigMgr OSD, but don’t lock it such that users can’t change it.  It turns out it is simple enough to do, but it requires changing the default wallpaper that comes with windows, which can be found at C:\Windows\Web\Wallpaper\Windows\img0.jpg.  If you check the security on that file however, you’ll notice that only the “TrustedInstaller” has full permissions to it, so to change it in the online OS requires messing with permissions.  Instead, you can change offline during the WinPE phase of the deployment, which bypasses the permissions problem.

This procedure requires MDT-integrated ConfigMgr 2012, and also requires Windows PowerShell to be added to your boot image.  I’ve tested on Windows 7 but should also work with Windows 8.1 as it uses the same location for the default wallpaper.

Create a Wallpaper

First, create your new default wallpaper.  The recommended resolution is 1920×1200, which is the same as the built-in default wallpaper.  Save it as “img0.jpg”

Create a PowerShell Script

Use the following code in a new PowerShell Script, and save it as “Set-DefaultWallpaper.ps1”

The script will load some task sequence variables, rename the existing default wallpaper to “img1.jpg”, and copy the new “img0.jpg” into the same directory.


# Get the TS variables
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$ScriptRoot = $tsenv.Value('ScriptRoot')
$OSDTargetSystemRoot =  $tsenv.Value('OSDTargetSystemRoot')

# Rename default wallpaper
Rename-Item $OSDTargetSystemRoot\Web\Wallpaper\Windows\img0.jpg img1.jpg -Force

# Copy new default wallpaper
Copy-Item $ScriptRoot\img0.jpg $OSDTargetSystemRoot\Web\Wallpaper\Windows -Force

Copy the files to your MDT Package directory

Copy the new wallpaper and the PowerShell script to the Scripts directory of your MDT package source files, and update the MDT Package to your distribution points.

Capture1

Capture2

Add a Task Sequence Step

Now edit your OSD task sequence and after the “Apply Operating System Image” Step and before the “Apply Windows Settings” step, add a “Run PowerShell Script” step from the MDT menu.

Capture3

Capture4

Add the path to the script file in the step, eg %SCRIPTROOT%\Set-DefaultWallpaper.ps1

Capture5
That’s it!  After OSD, your computer will have two files in the location C:\Windows\Web\Wallpaper\Windows, the new default wallpaper, and the old one renamed to “img1.jpg”.  Anyone who logs into the computer will get the new default wallpaper, and they are free to change it if they wish.

If you want to also change the colour scheme in Windows 8.1, there’s a nice post on the Coretech blog.

Calculate the Total Size of all Packages in a ConfigMgr Task Sequence

Today I got an interesting question – Is it possible to gather the sizes for all packages referred by a OS deployment task sequence?  I have a script that will find all package sizes for any group of packages you select, but not specifically for a task sequence.

But I found it can be done quite easily.  Simply enter your task sequence name and your site code, and the script will return all the packages referenced in that task sequence with their package sizes, and also give the total count and total size.

Capture

 


$TaskSequenceName = "Windows OS Deployment x64"
$SiteCode = "ABC"

$TSID = Get-WmiObject -Namespace ROOT\sms\Site_$SiteCode -Query "Select PackageID from SMS_PackageStatusDetailSummarizer where Name = '$TaskSequenceName'" |
    Select -ExpandProperty PackageID

$PKGs = Get-WmiObject -Namespace ROOT\sms\Site_$SiteCode -Query "Select * from SMS_TaskSequencePackageReference where PackageID = '$TSID'" | 
    Select @{N='PackageName';E={$_.ObjectName}},@{N='Size (MB)';E={$($_.SourceSize / 1KB).ToString(".00")}} | Sort PackageName

$Stats = $PKGs | Measure-Object "Size (MB)" -sum
$PKGs | Out-GridView -Title "Packages in ""$TaskSequenceName""   |   Total Packages: $($Stats.Count)   |   Total Size of Packages: $(($Stats.Sum / 1KB).ToString(".00")) GB"

New book! Deploy, Manage and Update Java Runtime with Configuration Manager and PowerShell. Available on Amazon.

I’m pleased to announce the release of my first IT Solution Guide – Deploy, Manage and Update Java Runtime Environment in the Enterprise with System Center Configuration Manager and PowerShell.  (Wow that’s a mouthful!)

IMG_4633

This step-by-step guide demonstrates how Configuration Manager can be used together with PowerShell to create a solution for managing the Java Runtime Environment in your organisation.  I cover:

  • Using the Application model to deploy Java with some custom scripting to uninstall previous versions and keep your registry clean
  • Using Compliance Settings to control how Java is configured on your client machines, for example locking settings in the Java Control Panel and ensuring the configuration is not changed
  • Using PowerShell to automate the task of deploying a new Java version
  • Reporting on your Java deployment/s, using either the built-in tools or using the provided custom deployment report created using Microsoft Excel with a SQL Server database connection

The solution would be particularly useful for enterprises that are security-conscious, and want to be able to control which Java version is installed and how it is configured on their Windows clients.  It would also be useful for IT administrators that need to deploy new versions of Java frequently and want to simplify the update process.  The framework of the solution could be customized and used to deploy and manage other applications that require frequent updates.

Available now worldwide on Amazon in Paperback and Kindle editions.

Amazon.com   Amazon.co.uk   CreateSpace