You are browsing the archive for powershell.

MMS 2015 unplugged : Preparing your App-V sequencer the right “automated” way

1:18 pm in App-V, App-V 5.0, applications, AppV, powershell, sequencing by Kenny Buntinx [MVP]


First of all – sorry for the delay as we promised to post this script much earlier. However as usual there are only 48 hours in a day :-)

If you attended our session “Sequencing applications with App-V 5.1 – Best Practices Edition”, Roy Essers and me showed you the latest techniques to prepare and analyze to virtualize apps using the latest version of Microsoft App-V. One of those best practices techniques is to prepare your sequencer the automated way  :

Another App-V MVP called Dan Gough originally created this script , but it wasn’t really a good solution in our eyes. The reason for that , was that it uses modules from chocolatey and boxstarter directly of the internet and we didn’t trust the big bad cloudy internet. Anyone could change the source code and inject malicious code anytime . Also changes made in revisions would also create an inconsistency in your build platform.

You will find the script below and we provide it AS-IS with no support :

# Disclaimer: This script is provided as-is, without warranty of any kind # # App-V sequencer / client generic build script # Version 1.0 - Dan Gough 04/07/15 # Version 1.1 - Fixed HideSCAHealth registry key # Version 1.2 - Fixed install on 32-bit Windows # Version 1.3 - Update for .NET 4.6 and Windows 10, better method of disabling Defender on Win8+ # Version 1.4 - Roy Essers - added native functions from chocolatey and boxstarter (so no need for those modules). # -sequencer only # -depends on local sources (dotnet, wmf, etc), if not found will try to download them from the internet # -added some features, onedrive removal. # planning to add option to (download and) install vcredists function Download-File() { <# .SYNOPSIS Downloads a file showing the progress of the download .DESCRIPTION This Script will download a file locally while showing the progress of the download .EXAMPLE .\Download-File.ps1 'http:\\\' .EXAMPLE .\Download-File.ps1 'http:\\\' 'C:\Temp\' .PARAMETER url url to be downloaded .PARAMETER localFile the local filename where the download should be placed .NOTES FileName : Download-File.ps1 Author : CrazyDave LastModified : 18 Jan 2011 9:39 AM PST #Requires -Version 2.0 #> param( [Parameter(Mandatory=$true)] [String] $url, [Parameter(Mandatory=$false)] [String] $localFile = (Join-Path $pwd.Path $url.SubString($url.LastIndexOf('/'))) ) begin { $client = New-Object System.Net.WebClient $Global:downloadComplete = $false $eventDataComplete = Register-ObjectEvent $client DownloadFileCompleted ` -SourceIdentifier WebClient.DownloadFileComplete ` -Action {$Global:downloadComplete = $true} $eventDataProgress = Register-ObjectEvent $client DownloadProgressChanged ` -SourceIdentifier WebClient.DownloadProgressChanged ` -Action { $Global:DPCEventArgs = $EventArgs } } process { Write-Progress -Activity 'Downloading file' -Status $url $client.DownloadFileAsync($url, $localFile) while (!($Global:downloadComplete)) { $pc = $Global:DPCEventArgs.ProgressPercentage if ($pc -ne $null) { Write-Progress -Activity 'Downloading file' -Status $url -PercentComplete $pc } } Write-Progress -Activity 'Downloading file' -Status $url -Complete } end { Unregister-Event -SourceIdentifier WebClient.DownloadProgressChanged Unregister-Event -SourceIdentifier WebClient.DownloadFileComplete $client.Dispose() $Global:downloadComplete = $null $Global:DPCEventArgs = $null Remove-Variable client Remove-Variable eventDataComplete Remove-Variable eventDataProgress [GC]::Collect() } } function Install-PinnedTaskBarItem { <# .SYNOPSIS Creates an item in the task bar linking to the provided path. .PARAMETER TargetFilePath The path to the application that should be launched when clicking on the task bar icon. .EXAMPLE Install-PinnedTaskBarItem "${env:ProgramFiles(x86)}\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe" This will create a Visual Studio task bar icon. #> param( [string] $targetFilePath ) Write-Debug "Running 'Install-PinnedTaskBarItem' with targetFilePath:`'$targetFilePath`'"; if (Test-Path($targetFilePath)) { $verb = "Pin To Taskbar" $path= split-path $targetFilePath $shell=new-object -com "Shell.Application" $folder=$shell.Namespace($path) $item = $folder.Parsename((split-path $targetFilePath -leaf)) $itemVerb = $item.Verbs() | ? {$_.Name.Replace("&","") -eq $verb} if($itemVerb -eq $null){ Write-Host "TaskBar verb not found for $item. It may have already been pinned" -ForegroundColor Yellow } else { $itemVerb.DoIt() } Write-host "`'$((Get-ChildItem $targetFilePath).Name)`' has been pinned to the task bar on your desktop." -ForegroundColor Green } else { $errorMessage = "`'$((Get-ChildItem $targetFilePath).Name)`' does not exist, not able to pin to task bar" } if($errorMessage){ Write-host $errorMessage -ForegroundColor Red throw $errorMessage } } function Set-StartScreenOptions { <# .SYNOPSIS Sets options for the Windows Start Screen. .PARAMETER EnableBootToDesktop When I sign in or close all apps on a screen, go to the desktop instead of Start .PARAMETER DisableBootToDesktop Disables the Boot to Desktop Option, see enableBootToDesktop .PARAMETER EnableDesktopBackgroundOnStart Show Desktop background on Start .PARAMETER DisableDesktopBackgroundOnStart Do not show Desktop background on Start .PARAMETER EnableShowStartOnActiveScreen Show Start on the display I'm using when I press the Windows logo key .PARAMETER DisableShowStartOnActiveScreen Disables the displaying of the Start screen on active screen, see enableShowStartOnActiveScreen .PARAMETER EnableShowAppsViewOnStartScreen Show the Apps view automatically when I go to Start PARAMETER DisableShowAppsViewOnStartScreen Disables the showing of Apps View when Start is activated, see enableShowAppsViewOnStartScreen .PARAMETER EnableSearchEverywhereInAppsView Search everywhere instead of just my apps when I search from the Apps View .PARAMETER DisableSearchEverywhereInAppsView Disables the searching of everywhere instead of just apps, see enableSearchEverywhereInAppsView .PARAMETER EnableListDesktopAppsFirst List desktop apps first in the Apps view when it's sorted by category .PARAMETER DisableListDesktopAppsFirst Disables the ability to list desktop apps first when sorted by category, see enableListDesktopAppsFirst .LINK #> [CmdletBinding()] param( [switch]$EnableBootToDesktop, [switch]$DisableBootToDesktop, [switch]$EnableDesktopBackgroundOnStart, [switch]$DisableDesktopBackgroundOnStart, [switch]$EnableShowStartOnActiveScreen, [switch]$DisableShowStartOnActiveScreen, [switch]$EnableShowAppsViewOnStartScreen, [switch]$DisableShowAppsViewOnStartScreen, [switch]$EnableSearchEverywhereInAppsView, [switch]$DisableSearchEverywhereInAppsView, [switch]$EnableListDesktopAppsFirst, [switch]$DisableListDesktopAppsFirst ) $PSBoundParameters.Keys | %{ if($_-like "En*"){ $other="Dis" + $_.Substring(2)} if($_-like "Dis*"){ $other="En" + $_.Substring(3)} if($PSBoundParameters[$_] -and $PSBoundParameters[$other]){ throw new-Object -TypeName ArgumentException "You may not set both $_ and $other. You can only set one." } } $key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer' $startPageKey = "$key\StartPage" $accentKey = "$key\Accent" if(Test-Path -Path $startPageKey) { if($enableBootToDesktop) { Set-ItemProperty -Path $startPageKey -Name 'OpenAtLogon' -Value 0 } if($disableBootToDesktop) { Set-ItemProperty -Path $startPageKey -Name 'OpenAtLogon' -Value 1 } if($enableShowStartOnActiveScreen) { Set-ItemProperty -Path $startPageKey -Name 'MonitorOverride' -Value 1 } if($disableShowStartOnActiveScreen) { Set-ItemProperty -Path $startPageKey -Name 'MonitorOverride' -Value 0 } if($enableShowAppsViewOnStartScreen) { Set-ItemProperty -Path $startPageKey -Name 'MakeAllAppsDefault' -Value 1 } if($disableShowAppsViewOnStartScreen) { Set-ItemProperty -Path $startPageKey -Name 'MakeAllAppsDefault' -Value 0 } if($enableSearchEverywhereInAppsView) { Set-ItemProperty -Path $startPageKey -Name 'GlobalSearchInApps' -Value 1 } if($disableSearchEverywhereInAppsView) { Set-ItemProperty -Path $startPageKey -Name 'GlobalSearchInApps' -Value 0 } if($enableListDesktopAppsFirst) { Set-ItemProperty -Path $startPageKey -Name 'DesktopFirst' -Value 1 } if($disableListDesktopAppsFirst) { Set-ItemProperty -Path $startPageKey -Name 'DesktopFirst' -Value 0 } } if(Test-Path -Path $accentKey) { if($EnableDesktopBackgroundOnStart) { Set-ItemProperty -Path $accentKey -Name 'MotionAccentId_v1.00' -Value 219 } if($DisableDesktopBackgroundOnStart) { Set-ItemProperty -Path $accentKey -Name 'MotionAccentId_v1.00' -Value 221 } } } function Get-CurrentUser { <# .SYNOPSIS Returns the domain and username of the currently logged in user. #> $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $parts = $identity.Name -split "\\" return @{Domain=$parts[0];Name=$parts[1]} } function Restart-Explorer { try{ Write-Host "Restarting the Windows Explorer process..." -ForegroundColor Cyan $user = Get-CurrentUser try { $explorer = Get-Process -Name explorer -ErrorAction stop -IncludeUserName } catch {$global:error.RemoveAt(0)} if($explorer -ne $null) { $explorer | ? { $_.UserName -eq "$($user.Domain)\$($user.Name)"} | Stop-Process -Force -ErrorAction Stop | Out-Null } Start-Sleep 1 if(!(Get-Process -Name explorer -ErrorAction SilentlyContinue)) { $global:error.RemoveAt(0) start-Process -FilePath explorer } } catch {$global:error.RemoveAt(0)} } function Set-WindowsExplorerOptions { <# .SYNOPSIS Sets options on the Windows Explorer shell .PARAMETER EnableShowHiddenFilesFoldersDrives If this flag is set, hidden files will be shown in Windows Explorer .PARAMETER DisableShowHiddenFilesFoldersDrives Disables the showing on hidden files in Windows Explorer, see EnableShowHiddenFilesFoldersDrives .PARAMETER EnableShowProtectedOSFiles If this flag is set, hidden Operating System files will be shown in Windows Explorer .PARAMETER DisableShowProtectedOSFiles Disables the showing of hidden Operating System Files in Windows Explorer, see EnableShowProtectedOSFiles .PARAMETER EnableShowFileExtensions Setting this switch will cause Windows Explorer to include the file extension in file names .PARAMETER DisableShowFileExtensions Disables the showing of file extension in file names, see EnableShowFileExtensions .PARAMETER EnableShowFullPathInTitleBar Setting this switch will cause Windows Explorer to show the full folder path in the Title Bar .PARAMETER DisableShowFullPathInTitleBar Disables the showing of the full path in Windows Explorer Title Bar, see EnableShowFullPathInTitleBar .LINK #> [CmdletBinding()] param( [switch]$EnableShowHiddenFilesFoldersDrives, [switch]$DisableShowHiddenFilesFoldersDrives, [switch]$EnableShowProtectedOSFiles, [switch]$DisableShowProtectedOSFiles, [switch]$EnableShowFileExtensions, [switch]$DisableShowFileExtensions, [switch]$EnableShowFullPathInTitleBar, [switch]$DisableShowFullPathInTitleBar ) $PSBoundParameters.Keys | % { if($_-like "En*"){ $other="Dis" + $_.Substring(2)} if($_-like "Dis*"){ $other="En" + $_.Substring(3)} if($PSBoundParameters[$_] -and $PSBoundParameters[$other]) { throw new-Object -TypeName ArgumentException "You may not set both $_ and $other. You can only set one." } } $key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer' $advancedKey = "$key\Advanced" $cabinetStateKey = "$key\CabinetState" Write-Host "Setting Windows Explorer options..." -ForegroundColor Cyan if(Test-Path -Path $advancedKey) { if($EnableShowHiddenFilesFoldersDrives) {Set-ItemProperty $advancedKey Hidden 1} if($DisableShowHiddenFilesFoldersDrives) {Set-ItemProperty $advancedKey Hidden 0} if($EnableShowFileExtensions) {Set-ItemProperty $advancedKey HideFileExt 0} if($DisableShowFileExtensions) {Set-ItemProperty $advancedKey HideFileExt 1} if($EnableShowProtectedOSFiles) {Set-ItemProperty $advancedKey ShowSuperHidden 1} if($DisableShowProtectedOSFiles) {Set-ItemProperty $advancedKey ShowSuperHidden 0} Restart-Explorer } if(Test-Path -Path $cabinetStateKey) { if($EnableShowFullPathInTitleBar) {Set-ItemProperty $cabinetStateKey FullPath 1} if($DisableShowFullPathInTitleBar) {Set-ItemProperty $cabinetStateKey FullPath 0} Restart-Explorer } } function Get-IsMicrosoftUpdateEnabled { <# .SYNOPSIS Returns $True if Microsoft Update is currently enabled .LINK #> # Default response to false, unless proven otherwise $installed = $false $serviceManager = New-Object -ComObject Microsoft.Update.ServiceManager -Strict $serviceManager.ClientApplicationID = "Sequencer" foreach ($service in $serviceManager.Services) { if( $service.Name -eq "Microsoft Update") { $installed = $true; break; } } return $installed } function Enable-MicrosoftUpdate { <# .SYNOPSIS Turns on Microsoft Update, so additional updates for other Microsoft products, installed on the system, will be included when running Windows Update. .LINK Disable-MicrsoftUpdate #> if(!(Get-IsMicrosoftUpdateEnabled)) { Write-Host "Microsoft Update is currently disabled." -ForegroundColor Yellow Write-Host "Enabling Microsoft Update..." -ForegroundColor Cyan $serviceManager = New-Object -ComObject Microsoft.Update.ServiceManager -Strict $serviceManager.ClientApplicationID = "Sequencer" $serviceManager.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"") } else { Write-Host "Microsoft Update is already enabled, no action will be taken." -ForegroundColor Green } } function Start-TimedSection { <# .SYNOPSIS Begins a timed section .DESCRIPTION A timed section is a portion of script that is timed. Used with Stop-TimedSection, the beginning and end of the section are logged to both the console and the log along with the amount of time elapsed. The function returns a guid that is used to identify the section when stopping it. .PARAMETER SectionName The Title or Label of the section being timed. This string is used in the logging to identify the section. .PARAMETER Verbose Instructs Start-TimedSection to write to the Verbose stream. Although this will always log messages to the Boxstarter log, it will only log to the console if the session's VerbosePreference is set to capture the Verbose stream or the -Verbose switch was set when calling Install-BoxstarterPackage. .EXAMPLE $session=Start-TimedSection "My First Section" Stop-TimedSection $session This creates a block as follows: + Boxstarter starting My First Section Some stuff happens here. + Boxstarter finished My First Section 00:00:00.2074282 .EXAMPLE Timed Sections can be nested or staggered. You can have multiple sections running at once. $session=Start-TimedSection "My First Section" $innerSession=Start-TimedSection "My Inner Section" Stop-TimedSection $innerSession Stop-TimedSection $session This creates a block as follows: + Boxstarter starting My First Section Some stuff happens here. ++ Boxstarter starting My Inner Section Some inner stuff happens here. ++ Boxstarter finished My Inner Section 00:00:00.1074282 Some more stuff happens here. + Boxstarter finished My First Section 00:00:00.2074282 Note that the number of '+' chars indicate nesting level. .EXAMPLE $session=Start-TimedSection "My First Section" -Verbose Stop-TimedSection $session This will write the start and finish messages to the Boxstarter log but will not write to the console unless the user has the the VerbosePreference variable or used the Verbose switch of Install-BoxstarterPackage. .NOTES If the SuppressLogging setting of the $Boxstarter variable is true, logging messages will be suppressed and not sent to the console or the log. .LINK Stop-TimedSection about_boxstarter_logging #> param( [string]$sectionName, [switch]$Verbose) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $guid = [guid]::NewGuid().ToString() $timerEntry=@{title=$sectionName;stopwatch=$stopwatch;verbose=$Verbose} if(!$script:boxstarterTimers) {$script:boxstarterTimers=@{}} $boxstarterTimers.$guid=$timerEntry $padCars="".PadLeft($boxstarterTimers.Count,"+") Write-host "$sectionName" -ForegroundColor Cyan return $guid } function Stop-TimedSection { <# .SYNOPSIS Ends a timed section .DESCRIPTION A timed section is a portion of script that is timed. Used with Start-TimedSection, the beginning and end of the section are logged to both the console and the log along with the amount of time elapsed. .PARAMETER SectionId The guid that was generated by Start-TimedSection and identifies which section is ending. .EXAMPLE $session=Start-TimedSection "My First Section" Stop-TimedSection $session This creates a block as follows: + Boxstarter starting My First Section Some stuff happens here. + Boxstarter finished My First Section 00:00:00.2074282 .EXAMPLE Timed Sections can be nested or staggered. You can have multiple sections running at once. $session=Start-TimedSection "My First Section" $innerSession=Start-TimedSection "My Inner Section" Stop-TimedSection $innerSession Stop-TimedSection $session This creates a block as follows: + Boxstarter starting My First Section Some stuff happens here. ++ Boxstarter starting My Inner Section Some inner stuff happens here. ++ Boxstarter finished My Inner Section 00:00:00.1074282 Some more stuff happens here. + Boxstarter finished My First Section 00:00:00.2074282 Note that the number of '+' chars indicate nesting level. .LINK Start-TimedSection about_boxstarter_logging #> param([string]$SectionId) $timerEntry=$script:boxstarterTimers.$SectionId if(!$timerEntry){return} $padCars="".PadLeft($boxstarterTimers.Count,"+") $script:boxstarterTimers.Remove($SectionId) $stopwatch = $timerEntry.stopwatch Write-Host "Finished $($timerEntry.Title) $($stopwatch.Elapsed.ToString())" -ForegroundColor Cyan $stopwatch.Stop() } function Install-WindowsUpdate { <# .SYNOPSIS Downloads and installs updates via Windows Update .DESCRIPTION This uses the windows update service to search, download and install updates. By default, only critical updates are included and a reboot will be induced if required. .PARAMETER GetUpdatesFromMS If this switch is set, the default windows update server, if any, is bypassed and windows update requests go to the public Microsoft Windows update service. .PARAMETER AcceptEula If any update requires a Eula acceptance, setting this switch will accept the Eula and allow the update to be installed. .PARAMETER SuppressReboots Setting this switch will suppress a reboot in the event that any update requires one. .PARAMETER Criteria The criteria used for searching updates. The default criteria is "IsHidden=0 and IsInstalled=0 and Type='Software'" which is effectively just critical updates. .LINK #> param( [switch]$getUpdatesFromMS, [switch]$acceptEula, [switch]$SuppressReboots, [string]$criteria="IsHidden=0 and IsInstalled=0 and Type='Software' and BrowseOnly=0" ) <# if(Get-IsRemote){ Invoke-FromTask @" Import-Module $($boxstarter.BaseDir)\boxstarter.WinConfig\Boxstarter.Winconfig.psd1 Install-WindowsUpdate -GetUpdatesFromMS:`$$GetUpdatesFromMS -AcceptEula:`$$AcceptEula -SuppressReboots -Criteria "$Criteria" "@ -IdleTimeout 0 -TotalTimeout 0 if(Test-PendingReboot){ Invoke-Reboot } return } #> try{ $searchSession=Start-TimedSection "Checking for updates..." $updateSession =new-object -comobject "Microsoft.Update.Session" $Downloader =$updateSession.CreateUpdateDownloader() $Installer =$updateSession.CreateUpdateInstaller() $Searcher =$updatesession.CreateUpdateSearcher() if($getUpdatesFromMS) { $Searcher.ServerSelection = 2 #2 is the Const for the Windows Update server } $wus=Get-WmiObject -Class Win32_Service -Filter "Name='wuauserv'" $origStatus=$wus.State $origStartupType=$wus.StartMode Write-warning "Update service is in the $origStatus state and its startup type is $origStartupType." -verbose if($origStartupType -eq "Auto"){ $origStartupType = "Automatic" } if($origStatus -eq "Stopped"){ if($origStartupType -eq "Disabled"){ Set-Service wuauserv -StartupType Automatic } Write-Host "Starting windows update service..." -ForegroundColor Cyan Start-Service -Name wuauserv } else { # Restart in case updates are running in the background Write-Host "Restarting windows update service..." -ForegroundColor Cyan Restart-Service -Name wuauserv -Force -WarningAction SilentlyContinue } $Result = $Searcher.Search($criteria) Stop-TimedSection $searchSession $totalUpdates = $Result.updates.count If ($totalUpdates -ne 0) { Write-Host "$($Result.updates.count) Updates found." -ForegroundColor Magenta $currentCount = 0 foreach($update in $result.updates) { ++$currentCount if(!($update.EulaAccepted)){ if($acceptEula) { $update.AcceptEula() } else { Write-host " * $($update.title) has a user agreement that must be accepted. Call Install-WindowsUpdate with the -AcceptEula parameter to accept all user agreements. This update will be ignored." -ForegroundColor Yellow continue } } $Result= $null if ($update.isDownloaded -eq "true" -and ($update.InstallationBehavior.CanRequestUserInput -eq $false )) { Write-Host " * $($update.title) already downloaded" -ForegroundColor Green $result = install-Update $update $currentCount $totalUpdates } elseif($update.InstallationBehavior.CanRequestUserInput -eq $true) { Write-Host " * $($update.title) Requires user input and will not be downloaded." -ForegroundColor Yellow } else { Download-Update $update $result = Install-Update $update $currentCount $totalUpdates } } if($result -ne $null -and $result.rebootRequired) { if($SuppressReboots) { Write-Host "A Restart is Required." -ForegroundColor Yellow } else { $Rebooting=$true Write-Host "Restart Required. Restarting now..." -ForegroundColor Yellow Stop-TimedSection $installSession if(test-path function:\Invoke-Reboot) { return Invoke-Reboot } else { Restart-Computer -force } } } } else{Write-Host "There is no update applicable to this machine." -ForegroundColor Yellow} } catch { Write-Host "There were problems installing updates: $($_.ToString())." -ForegroundColor Red throw } finally { if($origAUVal){ Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU -Name UseWuServer -Value $origAUVal -ErrorAction SilentlyContinue } if($origStatus -eq "Stopped") { Write-Host "Stopping win update service and setting its startup type to $origStartupType." -ForegroundColor Yellow Set-Service wuauserv -StartupType $origStartupType stop-service wuauserv -WarningAction SilentlyContinue } } } function Download-Update($update) { $downloadSession=Start-TimedSection "Download of $($update.Title)" $updates= new-Object -com "Microsoft.Update.UpdateColl" $updates.Add($update) | out-null $Downloader.Updates = $updates $Downloader.Download() | Out-Null Stop-TimedSection $downloadSession } function Install-Update($update, $currentCount, $totalUpdates) { $installSession=Start-TimedSection "Install $currentCount of $totalUpdates updates: $($update.Title)" $updates= new-Object -com "Microsoft.Update.UpdateColl" $updates.Add($update) | out-null $Installer.updates = $Updates try { $result = $Installer.Install() } catch { if(!($SuppressReboots)){ Restart-Computer -force } # Check for WU_E_INSTALL_NOT_ALLOWED if($_.Exception.HResult -eq -2146233087) { Write-Host "There is either an update in progress or there is a pending reboot blocking the install." -ForegroundColor Yellow $global:error.RemoveAt(0) } else { throw } } Stop-TimedSection $installSession return $result } if ([string]::IsNullOrEmpty($PSScriptRoot)) { $PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition } $Wow6432Node = "" ; if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { $Wow6432Node = "Wow6432Node" } try { Clear-Host Write-Host "Installing App-V pre-reqs..." -ForegroundColor Magenta if (([environment]::OSVersion.Version.Major -eq 10)) { if (-not (Test-Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5')) { Write-Host "Win10 detected, let's only enable NetFx3...." -ForegroundColor Yellow try { DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:$PSScriptRoot Write-host ".Net3.5 installed." -ForegroundColor Green } catch { Write-Warning ".Net3.5 failed to install." } } else {write-host "NetFx3 allready installed." -ForegroundColor Green} } else { if (-not (Test-Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5')) { try { $NetFx3 = (Join-Path $PSScriptRoot "dotnetfx35.exe") if(-not (Test-Path $NetFx3)) { Write-Host ".Net3.5 source not found, let's download..." -ForegroundColor Yellow Download-File -url "" -localFile $NetFx3 } Write-Host "Installing .Net3.5 ..." -ForegroundColor Cyan Start-Process -FilePath `"$NetFx3`" -ArgumentList "/q /norestart" -Wait | Out-Null Write-host ".Net3.5 installed." -ForegroundColor Green } catch { Write-Warning ".Net3.5 failed to install." } } else {write-host ".Net3.5 allready installed." -ForegroundColor Green} if (-not (Test-Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\.NETFramework,Version=v4.6')) { try { $NetFx4 = (Join-Path $PSScriptRoot "NDP46-KB3045557-x86-x64-AllOS-ENU.exe") if(-not (Test-Path $NetFx4)) { Write-Host ".Net4.6 source not found, let's download..." -ForegroundColor Yellow Download-File -url "" -localFile $NetFx4 } Write-Host "Installing .Net4.6..." -ForegroundColor Cyan Start-Process -FilePath `"$NetFx4`" -Wait | Out-Null Write-host ".Net4.6 installed." -ForegroundColor Green } catch { Write-Warning ".Net4.6 failed to install." } } else {write-host ".Net4.6 allready installed." -ForegroundColor Green} if ($host.version.Major -le 3) { try { If ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { $WMF4 = (Join-Path $PSScriptRoot "Windows6.1-KB2819745-x64-MultiPkg.msu") if(-not (Test-Path $WMF4)) { Write-Host ".WMF4x64 source not found, let's download..." -ForegroundColor Yellow Download-File -url "" -localFile $WMF4 } } else { $WMF4 = (Join-Path $PSScriptRoot "Windows6.1-KB2819745-x86-MultiPkg.msu") if(-not (Test-Path $WMF4)) { Write-Host ".WMF4x86 source not found, let's download..." -ForegroundColor Yellow Download-File -url "" -localFile $WMF4 } } Write-Host "Installing WMF4 ..." -ForegroundColor Cyan Start-Process -FilePath wusa.exe -ArgumentList `"$WMF4`", "/quiet", "/norestart" -Wait | Out-Null Write-host "WMF4 installed." -ForegroundColor Green } catch { Write-Warning "WMF4 failed to install." } } else {write-host "WMF4 or higher allready installed." -ForegroundColor Green} } Write-Host "Disabling automatic Windows Updates..." -ForegroundColor Cyan New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name AUOptions -PropertyType DWord -Value 1 -Force | Out-Null if (([environment]::OSVersion.Version.Major -eq 6 -and [environment]::OSVersion.Version.Minor -gt 1) -or ([environment]::OSVersion.Version.Major -gt 6)) { Write-Host "Disabling automatic Windows Store Updates..." -ForegroundColor Cyan New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsStore\WindowsUpdate" -Name AutoDownload -PropertyType DWord -Value 2 -Force | Out-Null } if (Test-Connection -ComputerName 2001:4860:4860::8888 -Count 3 -ErrorAction SilentlyContinue) # ping google for internet { Enable-MicrosoftUpdate try {Install-WindowsUpdate -Criteria "IsHidden=0 and IsInstalled=0 and Type='Software'" } catch { Install-WindowsUpdate -Criteria "IsHidden=0 and IsInstalled=0 and Type='Software'" } } Set-Service wuauserv -StartupType Disabled Write-Host "Disabling Security Center..." -ForegroundColor Cyan Set-Service -Name wscsvc -StartupType Disabled if ([environment]::OSVersion.Version.Major -lt 10) { New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -Name HideSCAHealth -PropertyType DWord -Value 1 -Force | Out-Null } else { if (!(Test-Path -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer")) { New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" | Out-Null } New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" -Name DisableNotificationCenter -PropertyType DWord -Value 1 -Force | Out-Null } Write-Host "Disabling System Restore..." -ForegroundColor Cyan Disable-ComputerRestore -Drive "C:\" Start-Process -FilePath vssadmin -ArgumentList "delete shadows /for=c: /all /quiet" -Wait | Out-Null Write-Host "Peforming SSD optimisations..." -ForegroundColor Cyan Set-Service -Name SysMain -StartupType Disabled New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" -Name EnableSuperfetch -PropertyType DWord -Value 0 -Force | Out-Null New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" -Name EnablePrefetcher -PropertyType DWord -Value 0 -Force | Out-Null New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\WMI\Autologger\ReadyBoot" -Name Start -PropertyType DWord -Value 0 -Force | Out-Null Write-Host "Disabling computer password change..." -ForegroundColor Cyan New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NetLogon\Parameters" -Name DisablePasswordChange -PropertyType DWord -Value 1 -Force | Out-Null Write-Host "Configuring power options..." -ForegroundColor Cyan powercfg -change -monitor-timeout-ac 0 powercfg -change -standby-timeout-ac 0 powercfg -h off Write-Host "Enabling Remote Desktop..." -ForegroundColor Cyan $oTS = Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace root\cimv2\terminalservices if($oTS -eq $null) { Write-host "Unable to locate terminalservices namespace. Remote Desktop is not enabled." -ForegroundColor Red } try { $oTS.SetAllowTsConnections(1,1) | out-null } catch { Write-host "There was a problem enabling remote desktop. Make sure your operating system supports remote desktop and there is no group policy preventing you from enabling it." -ForegroundColor Red } Write-Host "Configuring Windows Explorer options..." -ForegroundColor Cyan Set-WindowsExplorerOptions -EnableShowHiddenFilesFoldersDrives -EnableShowFileExtensions If ([environment]::OSVersion.Version.Major -eq 6 -and [environment]::OSVersion.Version.Minor -gt 1) { Set-StartScreenOptions -EnableBootToDesktop -EnableDesktopBackgroundOnStart -EnableShowStartOnActiveScreen -EnableShowAppsViewOnStartScreen -EnableSearchEverywhereInAppsView -EnableListDesktopAppsFirst } Write-Host "Pinning taskbar shortcuts..." -ForegroundColor Cyan Install-PinnedTaskBarItem "${env:ProgramFiles(x86)}\Internet Explorer\iexplore.exe" Install-PinnedTaskBarItem "$env:windir\System32\cmd.exe" Install-PinnedTaskBarItem "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" Install-PinnedTaskBarItem "$env:windir\regedit.exe" Install-PinnedTaskBarItem "$env:windir\Notepad.exe" Install-PinnedTaskBarItem "$env:windir\System32\SnippingTool.exe" Write-Host "Configuring Internet Explorer..." -ForegroundColor Cyan if(Test-Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}") { Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" -Name "IsInstalled" -Value 0 } if(Test-Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}") { Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" -Name "IsInstalled" -Value 0 } New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name DisableFirstRunCustomize -PropertyType DWord -Value 1 -Force | Out-Null New-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "Start Page" -PropertyType String -Value "about:blank" -Force | Out-Null Restart-Explorer Write-host "IE Enhanced Security Configuration (ESC) has been disabled." -ForegroundColor Green if (Get-Process "OneDrive" -ErrorAction SilentlyContinue) { Write-Host "Removing OneDrive..." -ForegroundColor Cyan Start-Process -FilePath "taskkill.exe" -ArgumentList "/f /im OneDrive.exe" -Wait | Out-Null Start-Process -FilePath "C:\Windows\SysWOW64\OneDriveSetup.exe" -ArgumentList "/uninstall" -Wait | Out-Null Set-ItemProperty -Path "HKCU:\Software\classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" -Name "System.IsPinnedToNameSpaceTree" -Value 0 -Force | Out-Null } Write-Host "Setting up App-V Sequencer build..." -ForegroundColor Magenta Write-Host "Disabling UAC..." -ForegroundColor Cyan New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system" -Name EnableLUA -PropertyType DWord -Value 0 -Force | Out-Null Write-Host "Disabling Windows Defender..." -ForegroundColor Cyan if ([environment]::OSVersion.Version.Major -eq 6 -and [environment]::OSVersion.Version.Minor -lt 2) { Set-Service -Name WinDefend -StartupType Disabled } else { New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name "DisableAntiSpyware" -PropertyType dword -Value 1 -Force | Out-Null } if (Get-Service CCmExec -ErrorAction SilentlyContinue) { Write-Host "Disabling SMS agent host..." -ForegroundColor Cyan Set-Service -Name WSearch -StartupType Disabled } Write-Host "Disabling Windows Search..." -ForegroundColor Cyan Set-Service -Name WSearch -StartupType Disabled Write-Host "Disabling FontCache..." -ForegroundColor Cyan Set-Service -Name FontCache3.0.0.0 -StartupType Disabled Write-Host "Disabling Sheduled Tasks..." -ForegroundColor Cyan schtasks /change /tn "\Microsoft\Windows\Application Experience\AitAgent" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\AutoChk\Proxy" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Customer Experience Improvement Program\Consolidator" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Defrag\ScheduledDefrag" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Diagnosis\Scheduled" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\DiskDiagnostic\Microsoft-Windows-DiskDiagnosticDataCollector" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\DiskDiagnostic\Microsoft-Windows-DiskDiagnosticResolver" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Maintenance\WinSAT" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Power Efficiency Diagnostics\AnalyzeSystem" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\RAC\RacTask" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Registry\RegIdleBackup" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Windows Error Reporting\QueueReporting" /disable | Out-Null schtasks /change /tn "\Microsoft\Windows\Windows Media Sharing\UpdateLibrary" /disable | Out-Null Write-Host "Creating dummy ODBC keys..." -ForegroundColor Cyan New-Item -Path "HKCU:\SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources" -Force | Out-Null New-Item -Path "HKLM:\SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources" -Force | Out-Null if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { New-Item -Path "HKLM:\SOFTWARE\Wow6432Node\ODBC\ODBC.INI\ODBC Data Sources" -Force | Out-Null } if (-not ($SeqVersion = (Get-command "C:\Program Files\Microsoft Application Virtualization\Sequencer\Sequencer.exe" -ErrorAction SilentlyContinue).FileVersionInfo.ProductVersion)) { try { $Sequencer = (Join-Path $PSScriptRoot "appv_sequencer_setup.exe") if(-not (Test-Path $Sequencer)) { Write-Host "Sequencer source not found, please download and run the script again..." -ForegroundColor Red } else { Write-Host "Installing sequencer..." -ForegroundColor Cyan Start-Process -FilePath `"$Sequencer`" -ArgumentList "/ACCEPTEULA /q" -Wait | Out-Null Write-host "Sequencer installed." -ForegroundColor Green Install-PinnedTaskBarItem "$env:ProgramFiles\Microsoft Application Virtualization\Sequencer\Sequencer.exe" } } catch { Write-Warning "Sequencer failed to install." } } else {write-host "Sequencer $SeqVersion allready installed." -ForegroundColor Green} if (-not (Test-Path "HKLM:\SOFTWARE\$Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{A79B1F5E-FEC1-4DD7-86F3-7EB2ED1D28F2}")) { try { $ACE = (Join-Path $PSScriptRoot "ACE.msi") if(-not (Test-Path $ACE)) { Write-Host "ACE source not found, let's download..." -ForegroundColor Yellow Download-File -url "" -localFile $ACE } Write-Host "Installing ACE ..." -ForegroundColor Cyan Start-Process -FilePath msiexec.exe -ArgumentList /i, `"$ACE`", RUNAPPLICATION=0, AI_QUICKLAUNCH_SH=1, AI_STARTMENU_SH=0, AI_STARTUP_SH=0, /qn -Wait | Out-Null Install-PinnedTaskBarItem "${env:ProgramFiles(x86)}\Virtual Engine\ACE\Ace.exe" Write-host "ACE installed." -ForegroundColor Green } catch { Write-Warning "ACE failed to install." } } else {write-host "ACE allready installed." -ForegroundColor Green} Write-Host "Add 'Copy as Path' Shellhandler..." -ForegroundColor Cyan Set-Location -LiteralPath "HKLM:\SOFTWARE\Classes\*\shell" | Out-Null New-Item . -Name "Copy as Path" -Force | Out-Null Set-Location -LiteralPath "HKLM:\SOFTWARE\Classes\*\shell\Copy as Path\" | Out-Null New-Item . -Name "command" -Value 'cmd.exe /c echo \"%1\"|clip' -Force | Out-Null New-Item -Path "HKLM:\SOFTWARE\Classes\Directory\shell" -Name "Copy as Path" -Force | Out-Null New-Item -Path "HKLM:\SOFTWARE\Classes\Directory\shell\Copy as Path" -Name "command" -Value 'cmd.exe /c echo \"%1\"|clip' -Force | Out-Null Set-Location $PSScriptRoot Write-Host "Optimising .NET assemblies..." -ForegroundColor Cyan Start-Process -FilePath "C:\Windows\Microsoft.NET\Framework\v4.0.30319\ngen.exe" -ArgumentList "executeQueuedItems /nologo /silent" -Wait if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { Start-Process -FilePath "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe" -ArgumentList "executeQueuedItems /nologo /silent" -Wait } Write-Host "Performing disk clean up..." -ForegroundColor Cyan New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Recycle Bin" -Name "StateFlags0001" -PropertyType dword -Value 2 -Force | Out-Null New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Update Cleanup" -Name "StateFlags0001" -PropertyType dword -Value 2 -Force | Out-Null Start-Process cleanmgr.exe -ArgumentList "/sagerun:1" -Wait Remove-Item $env:temp\* -Force -Recurse Remove-Item C:\Windows\Temp\* -Force -Recurse #Zero unused sectors to allow VHD to be compacted efficiently with Optimize-VHD cmdlet $env:SEE_MASK_NOZONECHECKS = 1 if (Test-Path (Join-Path $PSScriptRoot "sdelete.exe")) { Start-Process -FilePath (Join-Path $PSScriptRoot "sdelete.exe") -ArgumentList "-AcceptEula -s -z c:" -Wait } elseif (Test-Path "\\\tools\sdelete.exe") { Start-Process -FilePath "\\\tools\sdelete.exe" -ArgumentList "-AcceptEula -s -z c:" -Wait } else { Write-Warning "Unable to run sysinternals sdelete.exe..." } } catch { Write-Warning $($_.Exception.Message) throw } do { $choice = Read-Host -Prompt "Reboot now? [y/n]" If ($choice -eq "y") { Write-Host "restarting now..." -ForegroundColor Green;Restart-Computer -Force} If ($choice -eq "n") { Write-Host "Reboot first, and start your Sequence." -ForegroundColor Green} } until ($choice)

Hope it Helps ,

Kenny Buntinx & Roy Essers

“Workplace Join” with ADFS 3.0 Device Registration Services and our ‘Workplace Join Hitman’ PowerShell App to the rescue !

5:00 pm in ADFS 3.0, BRIFORUM, ConfigMgr, configmgr 2012 R2, drs, intune, powershell, SCCM 2012, sccm 2012 R2, Workplace Join by Kenny Buntinx [MVP]


Domain Join is what we have had for a long time, tight admin control, group policy, managing the desktop in full glory and control. "Workplace Join is much lighter, and is about authenticating an unknown device like a Surface RT, iOS or Android device. We will put a certificate on the device, and can challenge the device for this as part of claims based authentication to applications or other resources such as data, plus there is no admin control of the device, it remains under the control of the end user.

When coupled with BYO device management with a solution like Windows Intune, you can apply policy, deploy apps and control access to resources on machines that you otherwise have no control over."

Through the new Workplace Join feature within R2, AD FS becomes a focal point for mobile access in the enterprise and an integral component in the Microsoft Bring Your Own Device (BYOD) vision with Windows Intune. Workplace Join allows unmanaged or untrusted operating systems such as Windows RT / Windows 8.1 and IOS to be moved into a more controlled access context, by allowing their registration and affiliation with Active Directory.

Workplace Join is made possible by the Device Registration Service (DRS) that is included with the Active Directory Federation Role in Windows Server 2012 R2. When a device is Workplace Joined, the DRS provisions a device object in Active Directory and sets a certificate on the consumer device that is used to represent the device identity. The DRS is meant to be both internal and external facing. Companies that deploy both DRS and the Web Application Proxy will be able to Workplace Join devices from any internet connected location. To further secure this process, additional factors can be also used with Windows Azure Active Authentication (Phone Factor).

Lost Device Protection

As covered earlier, devices registered via ‘Workplace Join’ are registered within Active Directory in the following container ;

CN=<Device ID>,CN=RegisteredDevices,DC=mydomain,DC=com.

Lost devices can be denied access by disabling or deleting the appropriate object within AD (I moved the device objects to another OU to test this). Access through AD FS is immediately revoked for the workplace joined client.

From testing thus far, devices joined, left and re-registered via Workplace Join are currently not cleaned up within the ‘RegisteredDevices’ container. Some PowerShell scripting is currently required to enforce this. Later in this blog post we will explain you what we made available thru powershell.


This is question comes up all the time … how do I map a user to the devices that they have registered ?

1. The first attempt of Microsoft can be found here as this blog post is provided by Adam Hall . This is the output if you run the original script :


2. The second attempt to optimize the readout was done by a colleague Stijn Callebaut and it was already an improvement


The optimized code could be found below :

#user is provide by argument
if ($args.count -ne 1)
    Write-Host "Usage: GetRegisteredDeviceForUser.ps1 <user name>"
    exit 1 

#get user's sid
$domain = Get-ADDomain
$userName = $args[0]
$userSid = (New-Object System.Security.Principal.NTAccount($domain.NetBIOSName, $userName)).Translate([System.Security.Principal.SecurityIdentifier]).value

#search device object when registeredUser = user sid
$objDefaultNC = New-Object System.DirectoryServices.DirectoryEntry

$ldapPath = "LDAP://CN=RegisteredDevices," + $objDefaultNC.distinguishedName 
$objDeviceContainer = New-Object System.DirectoryServices.DirectoryEntry($ldapPath)
$strFilter = "(&(objectClass=msDS-Device)(msDS-RegisteredOwner=$userSid))"

$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDeviceContainer 
$objSearcher.PageSize = 100
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Onelevel"
$colResults = $objSearcher.FindAll()

foreach ($objResult in $colResults){
    $props = @{
    new-object PSObject -Property $props

3. But weren’t quite there yet. We wanted three things :

  • Easy browsing and easily find devices registered to a user
  • Easy selection of the devices needed
  • Delete the devices properly

A colleague working with me on a project and good friend Kurt Depre , learned to use Powershell Xaml thru MVP Kaido Jarvemets for our customer project and said he would make a great interface for my issue. After some days of testing we finally can show you the result of our powershell tool.

The tool is called Workplace Join Hitman and can let you do easy searching for devices that are workplace joined by a single user and revoke access by deleting the object .


You can download it and please rate the tool if you like it. It’s downloadable on Technet Gallery here :

It is not perfect , but it is intended to give you some idea’s to further automate the process when a device is stolen , lost or just discontinued. Next idea is to do that in a kind of Orchestrator workflow.

Hope it Helps , 

Kenny Buntinx

Enterprise Client Management MVP

System Center 2012 Configuration Manager : Troubleshooting and Resolving Content Mismatch Warnings on a Distribution Point with Powershell

8:09 pm in CM12, CM12 R2, CM12 SP1, powershell, R2, SCCM 2012, sccm 2012 R2, SCCM 2012 SP1, script by Kenny Buntinx [MVP]


You might see content mismatch warnings in System Center 2012 Configuration Manager when content validation runs and determines that there is a discrepancy between the expected list of packages in WMI on the distribution point and the packages in the content library as shown in the screenshot below.

You can see an example of this scenario in the following screenshot where a distribution point has a Warning state and there is a status message in the Details tab in the Details pane that shows there was a failure to retrieve the package list.


How to detect it and how to fix it manually is described here in the following article :

BUT , that is all manual work , and we hate that , don’t we Smile with tongue out . Doing this on around 35 Distribution points by hand isn’t an easy solution. Luckily we have an excellent PowerShell scripter in our team and all credits for creating this script goes to  Bart Serneels. He has written a PowerShell script to do all the work . He was happy to share this with you guys on the Technet Gallery.

As the package is not on the site and you must remove the package from WMI on the distribution point. The namespace to connect to is root\sccmdp. The class that contains the list of packages expected is SMS_PackagesInContLib.

Bart Serneels made a script to automate the removal of the invalid instances (those of which the package does not exist in the console anymore) in WMI on all distribution points. You can download it from


Hope it Helps ,

Kenny Buntinx

MVP Enterprise Client Management

Opalis 6.3 : Building a VMware/SCCM Opalis provisioning workflow

7:54 pm in ConfigMgr, ConfigMgr 2007, ConfigMgr 2007 R2, ConfigMgr SP2, configmgr2007, ConfigMgr2007 R3, Deployment, Installation, Opalis, Opalis 6.3, Operating System Deployment, powershell, sccm, SCCM 2007, SCCM 2007 R2, SCCM 2007 R3, SCCM 2007 SP2, sccm2007, Virtual machine, Vmware by Kenny Buntinx [MVP]

Recently we did a customer private cloud project where we used all the system center tooling ( , except for the hypervisor layer , which was VMware .

One of the scenarios that the customer had in mind , was to provision all there virtual servers with SCCM and we had to use Opalis to become the glue between VMware – BMC Remedy and System Center. In the first step of the project we didn’t use the Change request mechanism from BMC Remedy yet. Special thanks to my colleague Gunther Dewit for helping me out on this one .

**** Disclaimer **** – This is a very basic workflow – we will post improvements as we go along – it is for helping people moving forward **** Disclaimer ****

The workflow itself


Delivering input


The first step in creating a workflow is doing a custom start where we could input some necessary variables . The Custom Start Activity is used to create a generic starting point for Workflows. By adding parameters to the Custom Start Activity it can consume external data which can be passed to downstream Workflow Activities.


These are the parameters the workflow needs in further steps.  All the rest of the information that is residing in the data bus of Opalis  .

This input is required, without it, the workflow won’t start. A popup will be presented when starting the workflow.

Now that we have all the necessary input required, we can continue with the creation of the virtual machine. In order to create a virtual machine, we need to provide some parameters, some of them will come from the Custom start step, others will have to be adapted per workflow.


Creating the virtual machine



These are the required parameters.

  • Name: This is the name that will be given to the virtual machine, we will get it from the Custom Start  where we filled in a name.
  • Datastore: This is the datastore that will host the virtual machine disk, we will get it from the Custom Start  where we filled in the datastore.
  • DiskMB: Since it was decided to have a fixed disk with a size of 100GB, we filled it in directly instead of asking it in the first step.
  • DiskStorageFormat: This is the thick or thin format, thin was decided as the default format.
  • MemoryMB: This is the amount of memory that will be given to the virtual machine, we will get it from the Custom Start where we filled in an amount of memory.
  • NumCPU: This is the number of CPU’s that will be given to the virtual machine, we will get it from the Custom Start where we filled in the number of CPU’s we need.
  • CD: It was decided that all VM’s will have a cd drive so we set this to true.
  • VMSwapFilePolicy: This will set the swapfile policy the states where the swapfile will be saved, it was decided to do this in the VM itself.
  • VMHost: This is the physical host where the VM will be hosted, this integration pack cannot provision on cluster yet so you need to choose a physical host.
  • GuestID: This is the OS version that will be installed on the VM.
  • Folder: This is the foldername where the VM will be installed as shown in the ESX console.

You can add more details trough the “optional properties” button. If all goes well, the workflow has created the virtual machine now.

Now we need to change some things on the virtual machine.


Getting the network adapter settings from the created virtual machine


First we need to change the network settings. The VM name, we get from the Custom Start , since this is a read action, no further settings are needed.

Alternatively, you can specify some filters to narrow the data that you receive back.

Alternatively, you can specify some filters to narrow the data that you receive back.


Now we will delete all the network connection that VMware made by default because they are useless to us.


Removing the network adapters from the virtual machine



The Network Adapter name is data that we got back from the read action above and the VM name is still the name entered at the Custom Start .

This will remove all network adapters from the VM, alternatively, you can specify filters if you only want to delete a specific adapter.


Adding the production network adapter to the virtual machine


Now we need to add a network adapter to the VM. The VM name is still the name we entered at the Custom Start .


The NetworkName is the name of the network that you want your network adapter connecting to.

The StartConnected specifies if it will be connected to the network or only added without being connected.

The Type is e1000 as this is the only VMware adapter SCCM can work with.

Now we do another step to get the properties from the newly created adapter so we can use the information to input the computer into SCCM.


Getting the production network adapter settings from the virtual machine



Now that we collected the necessary information for SCCM, we can import the computer into SCCM.

This is done by a powershell script that needs to input parameters, the name and the MAC address.


Adding the computer to SCCM


Now that the computer is known is SCCM, we need to add it to the collection that has the OSD advertised to it.


The is done by the following step.


Adding the computer to an SCCM collection


In the collection field, you can enter 2 things, either the name of the collection or the ID of the collection. What you enter must match the collection value type. If you enter an ID as shown here, the value type must be ID as well. The same is true for the computer where we use the name from the Custom Start step so the value type is name in this case.


Now that the VM is created and provisioned in SCCM, we are ready to deploy the operating system on it.

So let’s power on the VM.


Powering on the virtual machine


The only thing you need to power on a VM is the name and we still get the from the first step.


Now that the VM is booting up, SCCM can start the task sequence to deploy an operating system on the VM.

Meanwhile, we will check the progress in Opalis.


Getting the virtual machine deployment status


The advertisement ID is the ID as it is known in SCCM and the computer name is still the name as we specified in the first step.


Looping the task

Now since the OSD deployment takes some time to complete, we will let the step loop until it gets a result back from SCCM.



It will recheck every 300 second and will do this 8 times or when it gets back from SCCM that the deployment was successful in order not keep the loop while the deployment was finished faster then in 8 loops.


Getting the deployment result


Now we need to output the result to any medium you want (logfile, mail, …), I do an output to a text file as an example.

Conditional progress

Now how does Opalis know when to write to which log file?

This can be regulated by double clicking on the arrows. This is the arrow toward the success file.


As you can see, it will only follow this arrow when SCCM outputs a succeeded message for the advertisement. If not, it will take the other path towards the failed log file.


So , It is not so easy to get it all together , but if I may give a great tip: ” Write down all steps of your manual flow  and then try to translate them into an opalis workflow “


Hope it Helps ,

Kenny Buntinx

Organize your Digital photos into folders using Powershell and Exif data

8:58 pm in dotnet, exif, powershell by The WMI guy

This post isn’t really systemcenter related directly, but with some creative thinking it does belong on this blog. Although sms is a large portion of my professional life, I still have a somewhat personal life as well. And in that personal life, I occasionally take some pictures. And since photography has progressed into digital pictures, I obviously take digital pictures, mainly of my 3-year old son Lennart. So far so good. The problem is that these digital photos need to be copied to computers to clear the memory cards for taking additional pictures.

This is usually were nightmares start happening, it starts with copying them to your laptop, because that is the machine that was in closest proximity when the memory card needed to be emptied. The next time around you put them on your mediacenter directly and so on. The main problem I have is that I usually have to do this in a hurry, because new pictures are going to be taken that day. Which means the files DSC?????.jpg are copied to some folder (usually called ToBeOrganized) without any descriptive name added to it. After taking pictures for a couple of months, getting this ToBeOrganized folder organized seems like a hell of a job. So I decided to call in Windows Powershell to assist.

For those of you that have been living under a rock for the post couple of months, Windows powershell is Microsoft’s new dos-box that has everyone running around overly excited. The neat thing about this dos-box is that it can access Dotnet classes, and that is exactly what I figured I would do to get my pictures organized.

I started of my endeavor by reading a blog post from James O’Neill’s blog. In his blog post James talks about accessing Exif data from within powershell, and that is exactly what I did, armed with the knowledge of that blog post I created one of my first Powershell scripts. The code isn’t really pretty, but it reads the Datapicturetaken property from the Exif data of all pictures in the folder where the script was launched from. Subsequently it copies all these files into a new folder called c:\organizedfotos. Underneath this folder you get a folder per year, followed by a folder per “picture date”. So in the end your folders are organized like this.



Now, all I need to do is analyze each folder to see what event triggered the creation of these pictures, rename the folder. Archive each year to Dvd, and finally decide which one we are going to print. How did I do all this will with a simple script. The Script looks like this:

# ==============================================================================================
# Microsoft PowerShell Source File — Created with SAPIEN Technologies PrimalScript 4.1
# NAME: OrgFotos.ps1
# AUTHOR:  Kim Oppalfens,
# DATE  : 12/2/2007
# COMMENT: Helps you organise your digital photos into subdirectory, based on the Exif data
# found inside the picture. Based on the date picture taken property the pictures will be organized into
# c:\organizedfotos\YYYY\DD-MM-YYYY
# ==============================================================================================

[reflection.assembly]::loadfile( “C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll”)

$Files = Get-ChildItem -recurse -filter *.jpg
foreach ($file in $Files)
  $foo=New-Object -TypeName system.drawing.bitmap -ArgumentList $file.fullname

#each character represents an ascii code number 0-10 is date
#10th character is space separator between date and time
#48 = 0 49 = 1 50 = 2 51 = 3 52 = 4 53 = 5 54 = 6 55 = 7 56 = 8 57 = 9 58 = :
#date is in YYYY/MM/DD format
  $date = $foo.GetPropertyItem(36867).value[0..9]
  $arYear = [Char]$date[0],[Char]$date[1],[Char]$date[2],[Char]$date[3]
  $arMonth = [Char]$date[5],[Char]$dateDevil
  $arDay = [Char]$dateMusic,[Char]$date[9]
  $strYear = [String]::Join(“”,$arYear)
  $strMonth = [String]::Join(“”,$arMonth)
  $strDay = [String]::Join(“”,$arDay)
  $DateTaken = $strDay + “-” + $strMonth + “-” + $strYear
  $TargetPath = “c:\organizedfotos\” + $strYear + “\” + $DateTaken
If (Test-Path $TargetPath)
    xcopy /Y/Q $file.FullName $TargetPath
    New-Item $TargetPath -Type Directory
    xcopy /Y/Q $file.FullName $TargetPath

The post isn’t entirely out-of SystemCenter Scope though, this has freed up quite some time, so I should be able to do some more Sms related posts in the next couple of weeks.

PS: Thomas, for the record, powershell is still just a silly new dos-box. Admitted, a dos-box in which you can do remarkable things every once in a while, but it stays a dos box 😉


“Everyone is an expert at something”
Kim Oppalfens – Sms Expert for lack of any other expertise
Windows Server System MVP – SMS