How can I in-place upgrade to Windows 10 1803 using Powershell App Deployment Toolkit and SCCM (System Center Configuration Manager)

Introduction

Update July 26, 2018: I have made an update to below content. Please find the new post on the link below. Note that the content in this post is still relevant.

Windows 10 1803 is out (old news I know). Nevertheless, its always a good idea to be ahead and start thinking and planning the upgrade of your environment. Configuration Manager offers a lot of flexibility in terms of servicing plans and the use of task sequences.

Task sequences is the preferred method in our environment, and I thought I’d share how you can deploy the Windows 10 1803 upgrade through the Powershell App Deployment Toolkit, some custom Powershell script and an application in the Configuration Manager Software Center. Curious? Read on. 🙂

1. Powershell App Deployment Toolkit

I’m not going into super details about the Powershell App Deployment Toolkit (PSADT), but will instead just provide you with a copy of the complete toolkit including my modifications for download: PSADT_1803Upgrade.zip (4060 downloads )

There are a few changes to it worth mentioning,  so here goes:

  • The installation has to happen as the currently logged on user and run in such context, therefore does the PSADT not require administrative rights (changed in AppDeployToolkitConfig.xml)
    • As of such, the registry path where the toolkit stores information changed to HKCU from HKLM (changed in AppDeployToolkitConfig.xml)
  • No balloon notifications (changed in AppDeployToolkitConfig.xml)
  • Slightly modified Show-InstallationPrompt to support cancellation of the deployment and line breaks of the message displayed
  • PSADT is running my custom powershell script explained in the next section (not included in the download on purpose)

2. Custom Powershell Script

This is actually where the most of the magic happens. This is the script you have to put into the Files folder of the Powershell App Deployment Toolkit. I left it out on purpose, forcing whoever to use this approach, to take a closer look on what’s happening. I will explain along the lines what’s happening inside the script. Note that it’s still referring to my stuff (nothing secret). Change accordingly.

Copy/paste and save the script as Execute-OSUpgrade.ps1 and put into the Files folder.

In short the script does following:

  • Loads the Write-Log function
  • Loads the Software Center as the currently logged on user
  • Retrieves the upgrade task sequence
  • Executes the upgrade task sequence
  • Writes to registry and logfile
# Log function - change path of logfile to suit your environment
function Write-Log {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("LogContent")]
        [string]$Message,
        
        # EDIT with your location for the local log file
        [Parameter(Mandatory=$false)]
        [Alias('LogPath')]
        [string]$Path='C:\ProgramData\Kromann Reumert\Execute-OSUpgrade.log',
        
        [Parameter(Mandatory=$false)]
        [ValidateSet("Error","Warn","Info")]
        [string]$Level="Info",
        
        [Parameter(Mandatory=$false)]
        [switch]$NoClobber
    )

    Begin
    {
        # Set VerbosePreference to Continue so that verbose messages are displayed.
        $VerbosePreference = 'Continue'
    }
    Process
    {
        
        # If the file already exists and NoClobber was specified, do not write to the log.
        if ((Test-Path $Path) -AND $NoClobber) {
            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
            Return
            }

        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
        elseif (!(Test-Path $Path)) {
            Write-Verbose "Creating $Path."
            $NewLogFile = New-Item $Path -Force -ItemType File
            }

        else {
            # Nothing to see here yet.
            }

        # Format Date for our Log File
        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

        # Write message to error, warning, or verbose pipeline and specify $LevelText
        switch ($Level) {
            'Error' {
                Write-Error $Message
                $LevelText = 'ERROR:'
                }
            'Warn' {
                Write-Warning $Message
                $LevelText = 'WARNING:'
                }
            'Info' {
                Write-Verbose $Message
                $LevelText = 'INFO:'
                }
            }
        
        # Write log entry to $Path
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
    }
    End
    {
    }
}

# Name of the task sequence. Modify to suit your environment
$TaskSequenceName = "Upgrade OS - TEST"

## Get the Software Center
try {
    $softwareCenter = New-Object -ComObject "UIResource.UIResourceMgr"
    Write-Log -Message "Successfully connected to the Software Center"
}
catch {
    Write-Log -Message "Cannot connect to the Software Center."
    exit 1
}

$TSReferencePackageIDs = @()
# Get the Task Sequence object
$taskSequence = $softwareCenter.GetAvailableApplications() | Where-Object { $_.PackageName -eq "$TaskSequenceName" }
        
if ($taskSequence) {
    $taskSequenceProgramID = $taskSequence.ID
    $taskSequencePackageID = $taskSequence.PackageID

    foreach ($i in $taskSequence.GetMemberPrograms()) {
         $TSReferencePackageIDs += $i.PackageID
    }
}
else {
    Write-Log -Message "Failed to retrieve the Windows 10 upgrade from the Software Center."
    exit 1    
}

# Execute the task sequence
$softwareCenter.ExecuteProgram($taskSequenceProgramID,$taskSequencePackageID,$true) | Out-Null

# Get current date
$Today = (Get-Date).ToString("dd/MM/yyyy")

# Write to registry if task sequence was successfully executed
if ($TaskSequence) {
    Write-Log -Level Info -Message "Task Sequence was successfully executed"
    $RegistryPath = "HKCU:\Software\Kromann Reumert"
        if (-not(Test-Path -Path $RegistryPath)) {
            New-Item -Path $RegistryPath –Force
            }
        New-ItemProperty -Path $RegistryPath -Name "1803UpgradeHasRun" -Value 1 -PropertyType "String" -Force
        New-ItemProperty -Path $RegistryPath -Name "1803RunDate" -Value $Today -PropertyType "String" -Force
    }
    
# Write to registry if task sequence failed to execute
else {
    Write-Log -Message "Task Sequence was not executed properly"
    $RegistryPath = "HKCU:\Software\Kromann Reumert"
    if (-not(Test-Path -Path $RegistryPath)) {
        New-Item -Path $RegistryPath –Force  
        }
    New-ItemProperty -Path $RegistryPath -Name "1803UpgradeFailed" -Value 1 -PropertyType "String" -Force
    New-ItemProperty -Path $RegistryPath -Name "1803FailedDate" -Value $Today -PropertyType "String" -Force
    exit 1
}

3. Configuration Manager

Next up is the application in Configuration Manager. Create a new application following below screenshots and instructions. (This is based off my edition of the PSADT and above Powershell script)

  • Manually specify the application information and click Next

  • Give the application a name and fill out as your desire and click Next

  • Specify details for the Software Center and select an icon

  • Add a new deployment type

  • Select Script Installer from the drop down menu and click Next

  • Give the Deployment Type a name and click Next

  • Specify the Content location and Installation program as shown above

  • Add a Detection Method on Add Clause

  • The detection method is going to be Registry and will point to the registry key made by the Execute-OSUpgrade.ps1 script. Modify to suit your changes

  • Make sure that the application has an application installation behavior as Install for user. Finish the wizard on Next.

Deployment

It goes without saying, but the application you just created and the upgrade task sequence intended to run, needs to be deployed (both as available in this scenario).

This is all basic Configuration Manager, so I’m not going into details here. For your reference, the application is being deployed to a collection consisting of users, whereas the task sequence is being deployed to a collection consisting of computers. Key here is, that the task sequence is available for the users deploying the application. If the task sequence isn’t available, the installation of the application running the script, will fail.

Your Software Center basically have to have both the application AND the task sequence shown as available. Something similar to this view:

End user experience

This illustrates how the experience is from an end users point of view.

  1. Manually clicking on the application in the Software Center (this can of course be a required deployment as well with a set deadline)
  2. Powershell App Deployment toolkit running with the chosen options
  3. Execute-OSUpgrade.ps1 being executed
  4. The upgrade task sequence being run and Windows 10 being upgraded

Final words

Using the Powershell App Deployment Toolkit gives you several advantages in ability to deferral, checking for disk space, checking for presence of powersupply and battery and adds a lot more visibility for the end user. I will walk through my upgrade task sequence in an upcoming blog post.

Please share and leave a comment if this was useful.

References:

https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
www.psappdeploytoolkit.com/

31 thoughts on “How can I in-place upgrade to Windows 10 1803 using Powershell App Deployment Toolkit and SCCM (System Center Configuration Manager)”

    • Thanks! Unfortunately not. I’m in a Windows 10 only environment, but I don’t see why the script shouldn’t work on Windows 7 as well 🙂

      Reply
  1. Just to add on this wonderful blogpost. The PS App Dep Toolkit has builtin check to ensure the the computer has a power adapter attached and it’s has a LAN/wired connection. However, the “Test-NetworkConnection” isn’t always super reliable – so test in your own environment.

    Code if needed (sorry for the spaghetti):

    #Test if virtual machine
    $ModelType = Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty Model

    if($ModelType -ne “Virtual Machine”){
    ##
    #Check for power connected
    Test-Battery
    $testbat = Test-Battery
    if(!$testbat){
    Show-InstallationPrompt -Title ‘Strømstik mangler’ -Message “Installationen kunne ikke finde et tilsluttet Strømstik.`n`nTilslut venligst et Strømstik og tryk: Prøv igen” -Icon ‘Exclamation’ -Timeout 300 -MinimizeWindows $True -MessageAlignment Left -ButtonRightText ‘Prøv igen’
    $testbat2 = Test-Battery
    if(!$testbat2){
    Show-InstallationPrompt -Title ‘Strømstik mangler’ -Message “Installationen kunne igen ikke finde et tilsluttet Strømstik.`n`nOpgraderingen afbrydes.” -Icon ‘Exclamation’ -Timeout 300 -MinimizeWindows $True -MessageAlignment Left -ButtonRightText ‘Øv’
    exit
    }
    }
    # Check for wired-network
    Test-NetworkConnection
    $testnet = Test-NetworkConnection
    if(!$testnet){
    Show-InstallationPrompt -Title ‘Netværkskabel mangler’ -Message “Installationen kunne ikke finde et tilsluttet netværkstik.`n`nTilslut venligst et netværkstik og tryk: Prøv igen” -Icon ‘Exclamation’ -Timeout 300 -MinimizeWindows $True -MessageAlignment Left -ButtonRightText ‘Prøv igen’
    $testnet2 = Test-NetworkConnection
    if(!$testnet2){
    Show-InstallationPrompt -Title ‘Netværkskabel mangler’ -Message “Installationen kunne igen ikke finde et tilsluttet netværkstik.`n`nOpgraderingen afbrydes.” -Icon ‘Exclamation’ -Timeout 300 -MinimizeWindows $True -MessageAlignment Left -ButtonRightText ‘Øv’
    exit
    }
    }
    }

    Reply
  2. Great blog.
    One thing I would change is the registry tattooing. I would create that registry key at the end of the task sequence if this was successful. In this way you can re-run the application if the TS fails to upgrade the OS.

    Reply
    • That’s actually not a bad idea. I think I came across that myself but hated that the application will fail. I might reconsider actually. Thanks 🙂

      Reply
  3. Hello,

    First thanks you this very great tool for the upgrade in place.

    In your video, i see “Windows is beiing upgraded ..” in full screen. I try to create this in XAML but it’s very difficult for me.

    Can you please share this code ? save me a lot of time for my project.

    Thanks you very much in advance and sorry for my english i’m french.

    Reply
  4. You create the PSAD as a application and in the box you say ‘if you click No it will retry in 2 hours’. It’s not possible to rerun an application at a set schedule of 2 hours so how did you get the 2 hours to work? Is 2 hours the period in which SCCM checks if a required application is installed and if not retries the installation and thus shows the PSAD again?

    Also… if a user refuses to attach the power adapter nothing actually happens after clicking retry and OK. Isn’t it possible to create an infinity retry loop?

    Reply
  5. I noticed when reading the updated blog. Thanks for the quick answer. We’ve been using the PSAD for quite a while now but I never thought of using it in ways like this. For our IPU we use OneVinn’s Windows 10 Upgrade Tools, but we ran into some challenges with systems not using the power adapter. Your post was of great use. Just trying to find a way to combine it all.

    Reply
  6. A few days ago I asked if the check for the Power Adapter could be an infinity loop that the user couldn’t escape by just clicking Try Again or OK. With this code the Messagebox will keep popping up until the user really attaches a power adapter. No escape possible unless the user kills the running task.

    #Check for connected power adapter
    if($ModelType -ne “Virtual Machine”) {

    Do {
    $Testbat = Test-Battery

    If(!$Testbat) {
    Show-InstallationPrompt -Title ‘Power adapter missing!’ -Message “The installation was unable to find an attached power adapter.`n`nPlease attach a power adapter and click try again” -Icon ‘Exclamation’ -Timeout 300 -MinimizeWindows $True -MessageAlignment Left -ButtonRightText ‘Try Again’

    }
    }
    Until($Testbat)
    }

    Credits to you for giving me the idea to use the PSAD Toolkit and to José Espitia for showing me how to correctly script a Do – Until loop. I am by no means a Powershell Guru 🙂

    In our IPU I’ve implemented a step which checks if the system is running without power attached and if so it pops up a messagebox. The user can only continue with the IPU by attaching the power cord. Works like a charm.

    Reply
  7. I can’t thank you enough for posting your two articles on this, I was working on the exact same scenario with a similar PSADT script I created and was having trouble getting mine to run in a User context without admin rights and still being able to use Deferral…your methods helped me get mine working flawlessly, kudos!

    Reply
  8. Hi Martin, thank you for your work!
    I am wondering though – if both App and TS are available, won’t it confuse Users? They can select a TS instead of the App… Are you hiding TS somehow?

    Reply
    • Hi and thank you for reaching out. It think that will vary based on the curiosity and level of skill of the individual user. You can reference the application directly using the URL. You never have to make the user aware of the actual TS. Also, I reference the application directly using a toast notification, so the user is never seeing the actual task sequence in this case 🙂

      Reply
  9. Hello Martin
    first of all, thank you so much for this script, it is exactly what i was looking for.

    unfortunately, i have a little error when launching it.
    it throws an error at line 107:
    $softwareCenter.ExecuteProgram($taskSequenceProgramID,$taskSequencePackageID,$true) | Out-Null

    the error is:
    An HRESULT E_FAIL error has been returned from a call to a COM component.
    + $softwareCenter.ExecuteProgram($taskSequenceProgramID,$taskSequencePa …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], COMException
    + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException

    in the logs, i see this before the error:
    “Successfully connected to the Software Center”
    and this one after the error:
    “Task Sequence was successfully executed”

    the registry keys “1803UpgradeHasRun” and “1803RunDate” are correctly created

    the Task Sequence runs normally when i launch it via SCCM

    If you can help me, that would be great.

    many thanks =)

    Reply
    • Uhm, I’m not sure. I would try to verify that the TS indeed is available in the Software Center and then run those lines executing the TS separately (don’t run the entire script). Does that work?

      Reply
  10. Hello Martin

    I was running the deployment in system context and not user context .
    after a few changes, it’s working good now.
    very nice script =)

    do you know if it’s possible to modify the duration of the defer time ?

    currently, it’s managed by SCCM who runs the deployment again when it fails but that config is fixed at 15 mins by my compny and i can’t change it because it will afect all the deployments in the console.

    changing that setting only for this deployment would be great.

    i’ve been told its not possible but maybe ther is some powershell trick to do it in PSAppDeploymentTool.

    thank you so much for your help

    Reply
  11. Hello Martin
    how are you ?

    sorry to bother you again with my issues, but i don’t find anything as close to what i need than your script and i’m really bad a powershell.

    is there a way to adapt these lines in order to execute an application instead of a tasksequence ?
    thank you


    # Name of the task sequence. Modify to suit your environment
    $TaskSequenceName = “Upgrade OS – TEST”

    ## Get the Software Center
    try {
    $softwareCenter = New-Object -ComObject “UIResource.UIResourceMgr”
    Write-Log -Message “Successfully connected to the Software Center”
    }
    catch {
    Write-Log -Message “Cannot connect to the Software Center.”
    exit 1
    }

    $TSReferencePackageIDs = @()
    # Get the Task Sequence object
    $taskSequence = $softwareCenter.GetAvailableApplications() | Where-Object { $_.PackageName -eq “$TaskSequenceName” }

    if ($taskSequence) {
    $taskSequenceProgramID = $taskSequence.ID
    $taskSequencePackageID = $taskSequence.PackageID

    foreach ($i in $taskSequence.GetMemberPrograms()) {
    $TSReferencePackageIDs += $i.PackageID
    }
    }
    else {
    Write-Log -Message “Failed to retrieve the Windows 10 upgrade from the Software Center.”
    exit 1
    }

    # Execute the task sequence
    $softwareCenter.ExecuteProgram($taskSequenceProgramID,$taskSequencePackageID,$true) | Out-Null

    Reply
  12. Curious as to why you created a separate file to execute the task sequence rather than running the commands inside the PSADT script?

    As you also able to share you In Place Upgrade task sequence?

    Reply
    • I’ve shred that already. I lie to keeo those things separate. Also easier for anything to edit, replce and what if it’s in a file and not inline in the script

      Reply

Leave a Reply to Martin Bengtsson Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.