Now that we’ve got the option to easily enable a Prestart command in the boot image, why not extend the functionality of the OSD capabilities even more. There are many great front ends for OSD in ConfigMgr out there that administrators are using in production today. The one I’ve created is not trying to replace any of those, or simply copy their functionality. Instead I’ve created a front end that performs a couple of tasks before the deployment process can begin. If any of these checks fails, the deployment process will simply be put on a hold until the problem is remediated. In this post I’ll demonstrate how to implement this front end in a boot image, so that you can take advantage of its features. Unfortunately I’ve not been able to come up with a good name for it, so please feel free to comment about the name if you think that you’ve got a better one! At this point I’m calling it the CM2012 OSD Front End.
Overview
- How does it work
- Get the CM2012 OSD Front End
- Enable a Prestart command in the boot image
- Run the front end in a Task Sequence
- See it in action
- Download the script
How does it work
This front end is built to perform 3 tasks. It will check whether a power adapter is connected or not, check if the specified Management Point is reachable and finally check if there’s an active Wireless connection connected. If any of these checks returns false, which means that they were not validated successfully, the front end will pause the deployment process and you’ll be able to remediate the issues. If all of the checks are successfully validated, the script will automatically count down from 10 and then terminate.
Get the CM2012 OSD Front End
In the section below called Download the script you can get your hands on the code, or just download it from the TechNet Gallery. I’d suggest that you save it as CM2012OSDFrontEnd.ps1 in a location where it’s accessible for the boot image to read the files. In my lab environment I’ve saved the file to:
\\CM01\ContentLibrary$\OSD\Packages\CM2012OSDFrontEnd
Enable a Prestart command in the boot image
1. In the Software Library under Operating Systems -> Boot Images, right-click on the boot image you’d like to enable with a Prestart command and choose Properties.
2. Go to the Customization tab and put a check mark in Enable prestart command and Include files for the prestart command.
3. Now specify the following settings (replace the source directory with the path for your environment where you saved the script):
- Command line:
cmd.exe /C powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File CM2012OSDFrontEnd.ps1 - Source directory:
\\CM01\ContentLibrary$\OSD\Packages\CM2012OSDFrontEnd
4. Click OK and update the Distribution Points when asked.
Now the next time you start the deployment process, you’ll have a nice front end doing some checks for you.
Note: The boot image that you’re enabling this front end to run on will have to be atleast WinPE 4.0 with the PowerShell components added.
Run the front end in a Task Sequence
If you’d like to have the front end running at the beginning of a task sequence instead, you can use this method:
1. Copy the CM2012OSDFrontEnd.ps1 script and ServiceUI.exe from a MDT 2012 Update 1 or MDT 2013 installation to a common path. The ServiceUI.exe (depending on boot image architecture) can be found in:
C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64
C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x86
2. Create a new Package (I’ve called it CM2012 OSD Front End) and specify the content source location to the path of where you saved the script and ServiceUI.exe. Remember to distribute the package.
3. Edit a task sequence that you wish to have the front to run in.
4. Add a new Run Command Line step in the beginning of the task sequence.
5. Configure the step with the following:
- Name:
CM2012 – OSD Front End - Command line:
serviceUI.exe -process:TSProgressUI.exe powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File CM2012OSDFrontEnd.ps1
Put a check mark in the Package box and browse for the package you created.
See it in action
Download the script
It’s important that you change the value for a Management Point to be checked on row 156. The default Management Point that it will check against is CM01.contoso.com. Just replace this FQDN with the Management Point in your environment.
function Load-Form { [System.Windows.Forms.Application]::EnableVisualStyles(); $Form.Controls.Add($OutputBox) $Form.Controls.Add($PictureBoxBranding) $Form.Controls.Add($ButtonRefresh) $Form.Controls.Add($LabelPower) $Form.Controls.Add($LabelMP) $Form.Controls.Add($LabelWireless) $Form.Controls.Add($LabelSystem) $Form.Controls.Add($LabelSystemText) $Form.Controls.Add($PowerPictureBoxSuccess) $Form.Controls.Add($PowerPictureBoxError) $Form.Controls.Add($MPPictureBoxSuccess) $Form.Controls.Add($MPPictureBoxError) $Form.Controls.Add($WirelessPictureBoxSuccess) $Form.Controls.Add($WirelessPictureBoxError) $Form.Controls.Add($GBCheck) $Form.Controls.Add($GBBranding) $ButtonRefresh.Enabled = $false $Form.Add_Shown({Start-Check}) $Form.Add_Shown({$Form.Activate()}) [void] $Form.ShowDialog() } function Start-Check { $PowerPictureBoxSuccess.Hide() $PowerPictureBoxError.Hide() $MPPictureBoxSuccess.Hide() $MPPictureBoxError.Hide() $WirelessPictureBoxSuccess.Hide() $WirelessPictureBoxError.Hide() $OutputBox.ResetText() [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Seconds 1 Write-OutputBox -OutputBoxMessage "Initializing test engine" -Type "INFO: " $NoNewLineCount = 0 while ($NoNewLineCount -lt 5) { $NoNewLineCount++ Write-OutputBox -OutputBoxMessage "-" -NoNewLine Start-Sleep -Seconds 1 } Get-SystemType if ((Check-PowerStatus -eq $true) -and (Check-ManagementPointStatus -eq $true) -and (Check-WirelessConnection -eq $true)) { Write-OutputBox -OutputBoxMessage "All checks passed successfully" -Type "INFO: " Write-OutputBox -OutputBoxMessage "Starting application termination count down" -Type "INFO: " $Form.Controls.Remove($ButtonRefresh) $Form.Controls.Add($ButtonOK) $ButtonOK.Enabled = $false $CountDown = 10 while ($CountDown -ge 1) { $CountDown-- $ButtonOK.Text = $CountDown [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Seconds 1 } if ($CountDown -eq 0) { $Form.Close() } } else { $ButtonRefresh.Enabled = $true } } function Get-SystemType { $SystemType = Get-WmiObject -Namespace "root\cimv2" -Class "Win32_SystemEnclosure" | Select-Object -ExpandProperty ChassisTypes if (($SystemType -eq 9) -or ($SystemType -eq 10) -or ($SystemType -eq 14)) { Write-OutputBox -OutputBoxMessage "Detected system is a laptop" -Type "INFO: " $LabelSystemText.Text = "Laptop" } else { Write-OutputBox -OutputBoxMessage "Detected system is a workstation" -Type "INFO: " $LabelSystemText.Text = "Workstation" } } function Test-TCPConnection { param(
[parameter(Mandatory=$true)]
$ManagementPoint,
[parameter(Mandatory=$true)]
[ValidateSet(“80″,”443”)] $Port ) try { $TCPConnection = New-Object System.Net.Sockets.TcpClient($ManagementPoint, $Port) if ($TCPConnection -ne $null) { Write-OutputBox -OutputBoxMessage “Successfully established a connection to:” -Type “INFO: ” Write-OutputBox -OutputBoxMessage “$($ManagementPoint) on port $($Port)” -Type “INFO: ” return $true } } catch { Write-OutputBox -OutputBoxMessage “$($_.Exception.Message)” -Type “ERROR: ” } finally { $TCPConnection.Close | Out-Null $TCPConnection.Dispose | Out-Null } } function Write-OutputBox { param(
[parameter(Mandatory=$true)]
[string]$OutputBoxMessage, [ValidateSet(“WARNING: “,”ERROR: “,”INFO: “)] [string]$Type, [switch]$NoNewLine ) Process { $DateTime = (Get-Date).ToLongTimeString() if ($NoNewLine -eq $true) { if ($OutputBox.Text.Length -eq 0) { $OutputBox.Text = “$($OutputBoxMessage)” [System.Windows.Forms.Application]::DoEvents() } else { $OutputBox.AppendText(“$($OutputBoxMessage)”) [System.Windows.Forms.Application]::DoEvents() } } else { if ($OutputBox.Text.Length -eq 0) { $OutputBox.Text = “$($DateTime) $($Type)$($OutputBoxMessage)`n” [System.Windows.Forms.Application]::DoEvents() } else { $OutputBox.AppendText(“`n$($DateTime) $($Type)$($OutputBoxMessage)”) [System.Windows.Forms.Application]::DoEvents() } } } } function Check-WirelessConnection { $WirelessNetworkAdapters = Get-WmiObject -Namespace “root\cimv2” -Class “Win32_NetworkAdapter” | Where-Object {($_.AdapterTypeID -eq 0) -and ($_.NetConnectionStatus -eq 2) -and ($_.NetConnectionID -match “Wireless”)} if (($WirelessNetworkAdapters | Measure-Object).Count -ge 1) { Write-OutputBox -OutputBoxMessage “A wireless connection was detected, please disconnect” -Type “ERROR: ” if ($WirelessPictureBoxError.Visible -eq $false) { $WirelessPictureBoxError.Visible = $true } [System.Windows.Forms.Application]::DoEvents() return $false } elseif (($WirelessNetworkAdapters | Measure-Object).Count -eq 0) { Write-OutputBox -OutputBoxMessage “No wireless connection detected” -Type “INFO: ” if ($WirelessPictureBoxSuccess.Visible -eq $false) { $WirelessPictureBoxSuccess.Visible = $true } [System.Windows.Forms.Application]::DoEvents() return $true } } function Check-ManagementPointStatus { $Connection = Test-TCPConnection -ManagementPoint “cm01.contoso.com” -Port 80 if ($Connection -eq $true) { if ($MPPictureBoxSuccess.Visible -eq $false) { $MPPictureBoxSuccess.Visible = $true } [System.Windows.Forms.Application]::DoEvents() return $true } else { if ($MPPictureBoxError.Visible -eq $false) { $MPPictureBoxError.Visible = $true } [System.Windows.Forms.Application]::DoEvents() return $false } } function Check-PowerStatus { if (Get-WmiObject -Namespace “root\cimv2” -Class Win32_SystemEnclosure | Where-Object {($_.ChassisTypes -eq 9) -or ($_.ChassisTypes -eq 10) -or ($_.ChassisTypes -eq 14)}) { $isLaptop = $true } else { $isWorkstation = $true } if ($isLaptop -eq $true) { $BatteryStatus = Get-WmiObject -Namespace “root\WMI” -Class BatteryStatus | Select-Object -ExpandProperty PowerOnline if ($BatteryStatus -eq $true) { if ($PowerPictureBoxSuccess.Visible -eq $false) { $PowerPictureBoxSuccess.Visible = $true } $PowerPictureBoxError.Hide() Write-OutputBox -OutputBoxMessage “Power adapter is connected” -Type “INFO: ” [System.Windows.Forms.Application]::DoEvents() return $true } else { $PowerPictureBoxSuccess.Hide() Write-OutputBox -OutputBoxMessage “Power adapter is not connected” -Type “WARNING: ” [System.Windows.Forms.Application]::DoEvents() return $false } } if ($isWorkstation -eq $true) { if ($PowerPictureBoxSuccess.Visible -eq $false) { $PowerPictureBoxSuccess.Visible = $true return $true } } } # Assemblies [void][System.Reflection.Assembly]::LoadWithPartialName(“System.Drawing”) [void][System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”) # Form $Form = New-Object System.Windows.Forms.Form $Form.Size = New-Object System.Drawing.Size(800,410) $Form.MinimumSize = New-Object System.Drawing.Size(800,410) $Form.MaximumSize = New-Object System.Drawing.Size(800,410) $Form.SizeGripStyle = “Hide” $Form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($PSHome + “\powershell.exe”) $Form.Text = “CM2012 OSD Front End 1.0” $Form.ControlBox = $false $Form.TopMost = $true # Buttons $ButtonRefresh = New-Object System.Windows.Forms.Button $ButtonRefresh.Location = New-Object System.Drawing.Size(630,320) $ButtonRefresh.Size = New-Object System.Drawing.Size(100,30) $ButtonRefresh.Text = “Refresh” $ButtonRefresh.TabIndex = “1” $ButtonRefresh.Add_Click({Start-Check}) $ButtonOK = New-Object System.Windows.Forms.Button $ButtonOK.Location = New-Object System.Drawing.Size(630,320) $ButtonOK.Size = New-Object System.Drawing.Size(100,30) $ButtonOK.Text = “OK” $ButtonOK.TabIndex = “1” $ButtonOK.Add_Click({$Form.Close()}) # Images $ImageFileSuccess = (Get-Item .\success.png) $ImageSuccess = [System.Drawing.Image]::Fromfile($ImageFileSuccess) $ImageFileError = (Get-Item .\error.png) $ImageError = [System.Drawing.Image]::Fromfile($ImageFileError) $ImageFileBranding = (Get-Item .\branding.png) $ImageBranding = [System.Drawing.Image]::Fromfile($ImageFileBranding) $PowerPictureBoxSuccess = New-Object Windows.Forms.PictureBox $PowerPictureBoxSuccess.Width = $ImageSuccess.Size.Width $PowerPictureBoxSuccess.Height = $ImageSuccess.Size.Height $PowerPictureBoxSuccess.Image = $ImageSuccess $PowerPictureBoxSuccess.Location = New-Object System.Drawing.Size(680,33) $PowerPictureBoxError = New-Object Windows.Forms.PictureBox $PowerPictureBoxError.Width = $ImageError.Size.Width $PowerPictureBoxError.Height = $ImageError.Size.Height $PowerPictureBoxError.Image = $ImageError $PowerPictureBoxError.Location = New-Object System.Drawing.Size(680,33) $MPPictureBoxSuccess = New-Object Windows.Forms.PictureBox $MPPictureBoxSuccess.Width = $ImageSuccess.Size.Width $MPPictureBoxSuccess.Height = $ImageSuccess.Size.Height $MPPictureBoxSuccess.Image = $ImageSuccess $MPPictureBoxSuccess.Location = New-Object System.Drawing.Size(680,58) $MPPictureBoxError = New-Object Windows.Forms.PictureBox $MPPictureBoxError.Width = $ImageError.Size.Width $MPPictureBoxError.Height = $ImageError.Size.Height $MPPictureBoxError.Image = $ImageError $MPPictureBoxError.Location = New-Object System.Drawing.Size(680,58) $WirelessPictureBoxSuccess = New-Object Windows.Forms.PictureBox $WirelessPictureBoxSuccess.Width = $ImageSuccess.Size.Width $WirelessPictureBoxSuccess.Height = $ImageSuccess.Size.Height $WirelessPictureBoxSuccess.Image = $ImageSuccess $WirelessPictureBoxSuccess.Location = New-Object System.Drawing.Size(680,83) $WirelessPictureBoxError = New-Object Windows.Forms.PictureBox $WirelessPictureBoxError.Width = $ImageError.Size.Width $WirelessPictureBoxError.Height = $ImageError.Size.Height $WirelessPictureBoxError.Image = $ImageError $WirelessPictureBoxError.Location = New-Object System.Drawing.Size(680,83) $PictureBoxBranding = New-Object Windows.Forms.PictureBox $PictureBoxBranding.Width = 170 $PictureBoxBranding.Height = 130 $PictureBoxBranding.Image = $ImageBranding $PictureBoxBranding.Location = New-Object System.Drawing.Size(585,165) $PictureBoxBranding.SizeMode = “Zoom” # Labels $LabelPower = New-Object System.Windows.Forms.Label $LabelPower.Location = New-Object System.Drawing.Size(585,35) $LabelPower.Size = New-Object System.Drawing.Size(90,20) $LabelPower.Text = “Power Adapter:” $LabelMP = New-Object System.Windows.Forms.Label $LabelMP.Location = New-Object System.Drawing.Size(585,60) $LabelMP.Size = New-Object System.Drawing.Size(90,20) $LabelMP.Text = “MP Access:” $LabelWireless = New-Object System.Windows.Forms.Label $LabelWireless.Location = New-Object System.Drawing.Size(585,85) $LabelWireless.Size = New-Object System.Drawing.Size(90,20) $LabelWireless.Text = “Wireless:” $LabelSystem = New-Object System.Windows.Forms.Label $LabelSystem.Location = New-Object System.Drawing.Size(585,110) $LabelSystem.Size = New-Object System.Drawing.Size(90,20) $LabelSystem.Text = “System:” $LabelSystemText = New-Object System.Windows.Forms.Label $LabelSystemText.Location = New-Object System.Drawing.Size(678,110) $LabelSystemText.Size = New-Object System.Drawing.Size(90,20) $LabelSystemText.Text = “–” # Groupboxes $GBCheck = New-Object System.Windows.Forms.GroupBox $GBCheck.Location = New-Object System.Drawing.Size(580,10) $GBCheck.Size = New-Object System.Drawing.Size(190,125) $GBCheck.Text = “Status” $GBBranding = New-Object System.Windows.Forms.GroupBox $GBBranding.Location = New-Object System.Drawing.Size(580,150) $GBBranding.Size = New-Object System.Drawing.Size(190,150) $GBBranding.Text = “Branding” # OutputBox $OutputBox = New-Object System.Windows.Forms.RichTextBox $OutputBox.Location = New-Object System.Drawing.Size(10,10) $OutputBox.Size = New-Object System.Drawing.Size(563,350) $OutputBox.Font = “Courier New” $OutputBox.BackColor = “white” $OutputBox.ReadOnly = $true $OutputBox.MultiLine = $true # Load form Load-Form
Hi,
I’ve an Issue. I’ve a pre-start command in my boot image. It works good if the task sequence starts from PXE-Boot. But if the task sequence starts from Software Center, at the “Restart in WinPe” task, after reboot, no pre-start command is launched.
How can I fix this?
Thanks.
Hello Nikolaj,
We’re currently using this front end in production. The configuration was done before I started to take over the SCCM administration. I was very familiar with the front end that MDT had. Is there a way to use the MDT front end for SCCM? Forgive my limited knowledge of SCCM administration. I’m just looking for more ways to limit access to the console for help desk employees. My biggest request would be the ability to capture user state from the front end. That would be ideal.
Jay
Hi James,
I’d suggest that you take a look at my new frontend called ConfigMgr OSD FrontEnd:
https://gallery.technet.microsoft.com/ConfigMgr-OSD-FrontEnd-100-55209031
Regards,
Nickolaj
Hi again,
My error was that the TS couldn’t find powershell.exe so I hade to add full path for powershell.exe.
Found an issue with the function that detects the wireless connection. On some systems or adapter types the NetConnectionID is not Wireless but Wi-Fi so you need to add -or $_.NetConnectionID -match “WiFi” -or $_.NetConnectionID -match “Wi-Fi” direct after “$_.NetConnectionID -match “Wireless””.
Hi Nikolaj,
Cant’t make the script work from TS. Get error “Process completed with exit code 4294967295”, Failed to run the action… Unknown error (Error: FFFFFFFF; Source Unknown)
Command line (copied from smsts.log) “C:\_SMSTaskSequence\Packages\S010010E\ServiceUIx64.exe” -process:TSProgressUI.exe powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File CM2012R2FrontEnd.ps1
ConfigMgr 1606, MDT 2013 SP1, PS & .NET activated in PE…
Why it is disappearing is. the file name in download and file name in the command is different CM2012OSDFrontEnd.ps1. CM2012R2FrontEnd.
In Enable prestart command
Wait, I thought Windows PE didn’t have a wireless stack, has that changed?
Why do you check after active Wireless connection? I would like to check if there is an active wired connection so that the OSD dosen’t start if the network cable isn’t connected.
A good tool to use. In our environment we turn off wireless during the imaging process. So, If, I remove or delete that part in the script it should work out, I guess.
Do, I need to remove any other line from the script?
function Check-WirelessConnection {
$WirelessNetworkAdapters = Get-WmiObject -Namespace “root\cimv2” -Class “Win32_NetworkAdapter” | Where-Object {($_.AdapterTypeID -eq 0) -and ($_.NetConnectionStatus -eq 2) -and ($_.NetConnectionID -match “Wireless”)}
if (($WirelessNetworkAdapters | Measure-Object).Count -ge 1) {
Write-OutputBox -OutputBoxMessage “A wireless connection was detected, please disconnect” -Type “ERROR: ”
if ($WirelessPictureBoxError.Visible -eq $false) {
$WirelessPictureBoxError.Visible = $true
}
[System.Windows.Forms.Application]::DoEvents()
return $false
}
elseif (($WirelessNetworkAdapters | Measure-Object).Count -eq 0) {
Write-OutputBox -OutputBoxMessage “No wireless connection detected” -Type “INFO: ”
if ($WirelessPictureBoxSuccess.Visible -eq $false) {
$WirelessPictureBoxSuccess.Visible = $true
}
[System.Windows.Forms.Application]::DoEvents()
return $true
}
}
Used the pre-start command in the BootImage. WinPE 5.0
Also added the Powershell components in the Boot Image
I enabled set-ExecutionPolicy on the Primary Site and after the boot image loads I see Powershell Open and disappear. I see it runs as Administrator. That’s about it.
Heres the command in the Boot Image:
cmd.exe /C powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File CM2012OSDFrontEnd.ps1
Thanks!
I double checked the paths and everything looks correct.
Hi,
From what you’re describing it seems to me that you’ve done it correctly. Did you include .NET Framework aswell in the boot image?
If you have the chance, could you send me screenshots of how it’s configured? Remember to “black-out” parts from the screenshots that you don’t want me to see, e.g. server names etc. You’ll find my email on the About page.
Regards,
Nickolaj
Didn’t work. I enabled powershell in the boot image, is there anything else I need?
Hi,
Which method did you use? Pre-start command or have you added the steps into your task sequence? Have you updated the boot image on the DP’s?
If you added the front-end in the TS, which steps did you create and how are they configured?
Which version of WinPE are you using, you need atleast WinPE 4.0.
Regards,
Nickolaj