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

70_1

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.

70_2

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
Nickolaj Andersen
Principal Consultant and Enterprise Mobility MVP. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows deployments and Automation. In 2015 Nickolaj was awarded as PowerShell Hero by the community for his script and tools contributions. Author of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService and a frequent speaker at user groups.

(786)

comments
  • fadeforth
    Posted at 01:51 February 10, 2014
    fadeforth
    Reply
    Author

    Didn’t work. I enabled powershell in the boot image, is there anything else I need?

    • Nickolaj
      Posted at 11:31 February 10, 2014
      Nickolaj
      Reply
      Author

      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

  • fadeforth
    Posted at 03:31 February 27, 2014
    fadeforth
    Reply
    Author

    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.

    • Nickolaj
      Posted at 17:03 March 2, 2014
      Nickolaj
      Reply
      Author

      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

  • Ram
    Posted at 13:37 July 20, 2014
    Ram
    Reply
    Author

    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
    }
    }

  • Thomas
    Posted at 08:59 April 20, 2016
    Thomas
    Reply
    Author

    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.

  • LP
    Posted at 15:36 July 8, 2016
    LP
    Reply
    Author

    Wait, I thought Windows PE didn’t have a wireless stack, has that changed?

  • Anthony
    Posted at 11:45 December 2, 2016
    Anthony
    Reply
    Author

    Why it is disappearing is. the file name in download and file name in the command is different CM2012OSDFrontEnd.ps1. CM2012R2FrontEnd.

    • Anthony
      Posted at 12:45 December 2, 2016
      Anthony
      Reply
      Author

      In Enable prestart command

  • Thomas
    Posted at 10:39 December 28, 2016
    Thomas
    Reply
    Author

    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…

  • Thomas
    Posted at 14:04 January 9, 2017
    Thomas
    Reply
    Author

    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””.

  • James
    Posted at 17:51 April 3, 2017
    James
    Reply
    Author

    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

  • Leave a Reply