Exclude updates during OS Deployment

With A LOT of inspiration from a blogpost by The Deploymentguys I wrote a couple of scripts that do not require internet access during OSD.

First, I have a script that pulls the KB and extracts KB-numbers to an XML-file.
Second, there is a script you run during OSD that reads the XML-file(s) and create thre TS env variable(s).

The script to update the XML-file from the online KB.

    [string] $KB = "2894518"

$url = "http://support.microsoft.com/kb/$($KB)"
    Write-Host "Retrieving list from $($url)"
    $result = Invoke-WebRequest $url -ErrorAction Stop
    THROW "Error retrieving KBs from $($url)"

$ExcludeKBs = @()
$result.AllElements | Where Class -eq "plink" | ForEach-Object {
    $pos = $_.innertext.indexof('/kb/') + 3

    #If Valid KB Hyperlink
    if ($pos -gt 3) {
        #String Cleansing, final ExcludeKB = 1234567
        $ExcludeKB = $_.innertext.Substring($pos,$_.innertext.Length-$POS).Trim().Replace('/','').Replace(')','').Trim()

        Write-Host "Found KB to exclude: $($ExcludeKB)"
        $ExcludeKBs += $ExcludeKB

if ($ExcludeKBs.Length -ne 0) { 
    $xmlPath = (Join-Path (Split-Path $MyInvocation.MyCommand.Definition -Parent) "exclude-auto-KB$($KB).xml")
    Write-Host "Exporting list to $($xmlPath)"
    $ExcludeKBs | Export-Clixml -Path $xmlPath

    Write-Host "Exit with code 0"
    Exit 0
} else {
    Write-Host "Exit with error code 99 (Didnt find any KBs)"
    Exit 99

Then the script to run during deployment.

    [string] $xmlFiles = "exclude-*.xml"

    $tsEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction SilentlyContinue
    Write-Host "Cant create TS Environment"
    $tsEnv = $null

$ExcludeKBs = @()
Get-ChildItem -Path "$(Split-Path $MyInvocation.MyCommand.Definition -Parent)*" -Include $xmlFiles | Foreach-Object {
    Write-Host "Importing KBs from $($_.FullName)"
    $ExcludeKBs += Import-Clixml -Path $_.FullName

$i = 1
$ExcludeKBs | Sort-Object -Unique | ForEach-Object {
    # Build variable number with zero-padding
    $tsi = "000$($i)"
    $tsi = $tsi.Substring(($tsi.ToString().Length - 3), 3)

    if ($tsEnv -ne $null) {
        Write-Host "Adding TS Variable:  WUMU_ExcludeKB$($tsi) = $($_)"
        $tsEnv.Value("WUMU_ExcludeKB$($tsi)") = $_
    } else {
        Write-Host "Cant add TS Variable:  WUMU_ExcludeKB$($tsi) = $($_)"

    $i ++

if ($tsEnv -eq $null) {
    Write-Host "Exit with error code 99 (Missing TS Environment)"
    Exit 99
} else {
    Write-Host "Exit with code 0"
    Exit 0

And, as a bonus you can specify multiple XML-files with updates you want to exclude… Name a file “exclude-blaha.xml” and let it have a content like this:

<Objs Version="" xmlns="http://schemas.microsoft.com/powershell/2004/04">

Then drop it in the same folder as the other XML and you should be good to go.


Find undefiened networks in netlogon.log

To find undefined networks in your AD you can parse the netlgon.log files on the domain controllers.
(This script will gather all errors you can of add some “If ($_.Error -like ‘NO_CLIENT_SITE*’) …” if you only want that kind of error.)

$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
Write-Host "Using domain $($domain.Name)"

# Copy files to %Temp%
($domain).DomainControllers | Foreach-Object {
    $netlogonSource = "\$($_.Name)Admin$debugnetlogon.log"
    $netlogonTarget = (Join-Path (Get-Item Env:Temp).Value "netlogon-$($_.Name.Replace($domain.name, ''))log")

    Write-Host "Copy from $($netlogonSource) to $($netlogonTarget)"
    Copy-Item -Path $netlogonSource -Destination $netlogonTarget -Force

# Process local files to hashtable
$networks = @{}
Get-ChildItem -Path "$((Get-Item Env:Temp).Value)netlogon*.log" | Sort-Object FullName | ForEach-Object {
    Write-Host "Processing $($_.FullName) " -NoNewline

    Import-Csv $_.FullName -Delimiter ' ' -Header Date, Time, Domain, Error, Name, IPAddress | ForEach-Object {
        if (! $networks.ContainsKey($_.IPAddress)) {
            # IP not in list, adding
            Write-Host "." -NoNewline -ForegroundColor Green
            $networks.Add($_.IPAddress, "$($_.Domain)$($_.Name) $($_.Error) $($_.Date) $($_.Time)")
        } else {
            # Allready there
            Write-Host "." -NoNewline -ForegroundColor Red
    Write-Host ""

# Export to new CSV
$outFile = "$((Get-Item Env:Temp).Value)netlogonData.csv"
Write-Host "Export data to $($outFile)"
$networks.GetEnumerator() | Select-Object Name, Value | Export-Csv -Path $outFile -Force 

# Remove temporary netlogon-files
Get-ChildItem -Path "$((Get-Item Env:Temp).Value)netlogon*.log" | Remove-Item -Force

Green dot – Unique IP found
Red dot – Duplicate (will not be added to list)

And in the end there will be a CSV-file in %Temp% that you can use.


Remove old logfiles

Want to clean out old logfiles from IIS (or other products)?

    [int] $daysBack = 7,
    [string] $logPath = "C:InetpubLogsLogFiles"

Get-ChildItem $logPath -Recurse -Include *.LOG | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(0-$daysBack)} | ForEach-Object {
	Write-Host "Processing: " -ForegroundColor Yellow -NoNewline
	Write-Host $_.FullName -ForegroundColor White -NoNewline
	$span = New-TimeSpan $_.CreationTime $(get-date)
	Write-Host " $($span.Days) days old" -ForegroundColor Yellow -NoNewline

	TRY {
        Remove-Item $_.FullName -Force -ErrorAction Stop
        Write-Host " [Deleted]" -ForegroundColor Green

    CATCH {
        Write-Host " [Can't delete]" -ForegroundColor Red

Get Powershell ISE to run scripts with -Verbose flag

Missing an easy way to run your scripts from ISE with the -Verbose or -Debug flag?

Easy to add… Open up your Microsoft.PowerShellISE_profile.ps1 file and add the following lines:

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Run with -Verbose', { Invoke-Expression -Command ". '$($psISE.CurrentFile.FullPath)' -Verbose" }, 'Ctrl+F5') | Out-Null
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Run with -Debug',   { Invoke-Expression -Command ". '$($psISE.CurrentFile.FullPath)' -Debug" }, 'Ctrl+F6') | Out-Null

Now when you restart ISE you should see the options under the Add-Ons menu.

Create Site Roles Collections

Quick and easy way to create device collections based on site roles:

$wmiParams = @{
    "Namespace" = "rootSMSsite_ABC";
    "Query" = "SELECT RoleName FROM SMS_SystemResourceList"

Get-wmiobject @wmiParams  | Group-Object RoleName | ForEach-Object {
    Write-Verbose "Creating collection for role $($_.Name)"
    $newCollection = New-CMDeviceCollection -Name "ConfigMgr-Role $($_.Name)" -LimitingCollectionID "SMS00001" -RefreshType Periodic -RefreshSchedule (New-CMSchedule -RecurInterval Days -RecurCount 4 -Start (Get-Date))
    $query = "SELECT * FROM SMS_R_System WHERE SMS_R_System.SystemRoles = '$($_.Name)'"
    Add-CMDeviceCollectionQueryMembershipRule -CollectionId $newCollection.CollectionID -QueryExpression $query -RuleName "CMRole-$($_.Name)"

You will end up with collections like:
– ConfigMgr-Role SMS Distribution Point
– ConfigMgr-Role SMS Device Management Point
– ConfigMgr-Role … and so on …

Stand Alone Media and USB 3

I’m testing some Stand Alone USB Media for deployment of Windows 7 and 8…

Ran in to a few problems…

First off, it’s not to easy to find a USB-Stick with at least 32 Gb that is bootable.
We found that Kingston DataTraveler R3.0 (rubber like casing) works OK to boot from…

Second, keep the volume size under 32 GB.
Since it will be a FAT32 Volume you need to create a partition at 32 (or less) GB. (Remember that it needs to be active)

Third, when using a USB3 port on the computer you will probably run in to problems if you try to install USB3-drivers.
Workaround is to plug the stick in to a USB2 port.

Fourth, when only having USB3 ports you can run in to issues accessing the files.
A simple workaround is to get a simple USB-Hub that uses USB2… with that you will downgrade your USB3-stick to a USB2-stick and everything works just fine. 🙂

Fifht, when using custom scripts in MDT you might run in to issues if you use “Start in” and then point out something like “%deployroot%MyScripts” as folder and a command line like “cscript script.vbs”
To solve this, use a command line like “cscript %DeployRoot%MyScriptsscript.vbs” and empty out the “Start in” folder.

Get filename in ConfigMgr 2012 ContentLib

In ConfigMgr 2007 it was kind of convinient to be able to edit files directly on a DP, in ConfigMgr 2012 that isn’t to easy if you are using Content Library.

There are a few ways to find out where the files are actually stored, here is one way:

(Will give you the path to somefile.xml in package ABC01234)

	$siteCode = "ABC",
	$FileName = "somefile.xml",
	$PackageID = "ABC01234",
	$ContentLib = "D:SCCMContentLib"
Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + 'ConfigurationManager.psd1')
Set-Location "$($siteCode):"

$package = Get-CMPackage -ID $PackageID
$hash = (Get-Content "$($ContentLib)DataLib$($package.PackageID).$($package.StoredPkgVersion)$($FileName).INI" | Where-Object { $_ -like "Hash=*" }).Replace("Hash=", "")
$storePath =  Join-Path "$($ContentLib)FileLib" "$($hash.Substring(0,4))$($hash)"

$sourcePath = Join-Path $package.PkgSourcePath $FileName

Write-Host "Source: $($sourcePath)"
Write-Host "Store : $($storePath)"

Will result in something like this:

Source: \serversharepathtopackage sourcesomefile.xml
Store : D:SCCMContentLibFileLibA123A1234567890ABCDEF1234567890ABCDEF

And with this you can edit the XML-file directly on the store instead of editing source, update DPs, wait for processing, wait some more…

… but, I’m not saying that I recommend anyone to edit files directly in the store. 😉