Intune Win32 App: Run huge app updates with Powershell

Introduction Intune Win32 apps can register apps up to 30GB in size. However, registering large apps takes a considerable amount of time at the upload stage, making it difficult to do this manually. I used the MS Graph Powershell sample to see if there was a way to upload more efficiently. Available scripts The scripts here allow you to do everything from registering to updating Win32 apps, and it seems to have improved usability in many ways. https://github.com/MSEndpointMgr/IntuneWin32App Meanwhile, the following sample is available in the official Microsoft Graph SDK powershell. https://github.com/microsoft/mggraph-intune-samples https://github.com/microsoft/mggraph-intune-samples/tree/main/LOB_Application https://github.com/microsoft/mggraph-intune-samples/blob/main/LOB_Application/Win32_Application_Update.ps1 I would like to try using the official MS version if possible, so I will experiment with this sample. Testing and modification For testing purposes, create an install.intunewin file of about 8GB in advance. Also, register the Win32 app with empty content in advance. Then, try running it like this by calling the sample function. # Now we'll dot source the sample file so that we can use the functions. . ".\Win32_Application_Update.ps1" # This is a must-have spell before you can use Graph. It authenticates you. Connect-MgGraph # Specify the ID of the previously registered Win32 app and the path of the intunewin file that you created. Invoke-Win32AppUpdate -AppId "2bdc5c92-cc6b-477d-9ee2-7b2624136910" -UpdateAppContentOnly $true -SourceFile ".\install.intunewin" You can also check the Win32 app ID in your browser as follows. When the target app is open, you can see the long ID in the address bar. And when I tried to run it, I encountered a number of problems. I'll solve them one by one. Trouble 1 This is simple. The file size probably doesn't fit into Uint32, so change the Uint32 to Uint64 to solve the problem. # Upload the file to Azure Storage Write-Host "Uploading the file to Azure Storage..." -ForegroundColor Yellow $file = WaitForFileProcessing $fileUri "AzureStorageUriRequest" [UInt64]$BlockSizeMB = 4 UploadFileToAzureStorage $file.azureStorageUri $IntuneWinFile $BlockSizeMB $fileUri Trouble #2 After updating, the upload proceeded for a while, but then I got this error. What does 403 mean? I looked at the aforementioned IntuneWIn32App and other sources, and it seems that the Win32 app uploads to Azure Blob, but the SAS has a time limit. If you don't update it periodically even during upload, you'll get this error. So I improved the UploadFileToAzureStorage function and added SAS Renewal as logic. It updates every 450000 msec = 7.5 minutes. function UploadFileToAzureStorage($sasUri, $filepath, $blockSizeMB, $fileUri) { # Chunk size in MiB $chunkSizeInBytes = (1024 * 1024 * $blockSizeMB) # Read the whole file and find the total chunks. #[byte[]]$bytes = Get-Content $filepath -Encoding byte; # Using ReadAllBytes method as the Get-Content used alot of memory on the machine $fileStream = [System.IO.File]::OpenRead($filepath) $chunks = [Math]::Ceiling($fileStream.Length / $chunkSizeInBytes) # Upload each chunk. $ids = @() $cc = 1 $chunk = 0 # Start the timer for SAS URI renewal $SASRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew() while ($fileStream.Position -lt $fileStream.Length) { $id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000"))) $ids += $id $size = [Math]::Min($chunkSizeInBytes, $fileStream.Length - $fileStream.Position) $body = New-Object byte[] $size $fileStream.Read($body, 0, $size) > $null $totalBytes += $size Write-Progress -Activity "Uploading File to Azure Storage" -Status "Uploading chunk $cc of $chunks" -PercentComplete ($cc / $chunks * 100) $cc++ UploadAzureStorageChunk $sasUri $id $body | Out-Null $chunk++ # Ensure the SAS Uri is renewed if ($SASRenewalTimer.ElapsedMilliseconds -ge 450000) { Write-Host -Message "SAS Uri renewal is required, attempting to renew" $RenewSASURIRequest = Invoke-MgGraphRequest -uri "$($fileUri)/renewUpload" -Method "POST" -Body "{}" $Stage = "AzureStorageUriRenewal" do { $GraphRequest = Invoke-MgGraphRequest -uri $fileUri -Method "GET" switch ($GraphRequest.uploadState) { "$($Stage)Pending" { Write-Host -Message "Intune service request for operation '$($Stage)' is in pending state, sleeping for 10 seconds" Start-Sleep -Seconds 10 } "$($Stage)Failed" { Write-Host -Message "Intune service request fo

Apr 30, 2025 - 15:03
 0
Intune Win32 App: Run huge app updates with Powershell

Introduction

Intune Win32 apps can register apps up to 30GB in size. However, registering large apps takes a considerable amount of time at the upload stage, making it difficult to do this manually.
I used the MS Graph Powershell sample to see if there was a way to upload more efficiently.

Available scripts

The scripts here allow you to do everything from registering to updating Win32 apps, and it seems to have improved usability in many ways.

https://github.com/MSEndpointMgr/IntuneWin32App

Meanwhile, the following sample is available in the official Microsoft Graph SDK powershell.

https://github.com/microsoft/mggraph-intune-samples
https://github.com/microsoft/mggraph-intune-samples/tree/main/LOB_Application
https://github.com/microsoft/mggraph-intune-samples/blob/main/LOB_Application/Win32_Application_Update.ps1

I would like to try using the official MS version if possible, so I will experiment with this sample.

Testing and modification

For testing purposes, create an install.intunewin file of about 8GB in advance.

Also, register the Win32 app with empty content in advance.

Then, try running it like this by calling the sample function.

# Now we'll dot source the sample file so that we can use the functions.
. ".\Win32_Application_Update.ps1"

# This is a must-have spell before you can use Graph. It authenticates you.
Connect-MgGraph

# Specify the ID of the previously registered Win32 app and the path of the intunewin file that you created.
Invoke-Win32AppUpdate -AppId "2bdc5c92-cc6b-477d-9ee2-7b2624136910" -UpdateAppContentOnly $true -SourceFile ".\install.intunewin"

You can also check the Win32 app ID in your browser as follows.
When the target app is open, you can see the long ID in the address bar.

Image description

And when I tried to run it, I encountered a number of problems. I'll solve them one by one.

Trouble 1

Image description

This is simple. The file size probably doesn't fit into Uint32, so change the Uint32 to Uint64 to solve the problem.


        # Upload the file to Azure Storage
        Write-Host "Uploading the file to Azure Storage..." -ForegroundColor Yellow
        $file = WaitForFileProcessing $fileUri "AzureStorageUriRequest"
        [UInt64]$BlockSizeMB = 4
        UploadFileToAzureStorage $file.azureStorageUri $IntuneWinFile $BlockSizeMB  $fileUri

Trouble #2

After updating, the upload proceeded for a while, but then I got this error.

Image description

What does 403 mean? I looked at the aforementioned IntuneWIn32App and other sources, and it seems that the Win32 app uploads to Azure Blob, but the SAS has a time limit. If you don't update it periodically even during upload, you'll get this error. So I improved the UploadFileToAzureStorage function and added SAS Renewal as logic. It updates every 450000 msec = 7.5 minutes.

function UploadFileToAzureStorage($sasUri, $filepath, $blockSizeMB,  $fileUri) {
    # Chunk size in MiB
    $chunkSizeInBytes = (1024 * 1024 * $blockSizeMB)

    # Read the whole file and find the total chunks.
    #[byte[]]$bytes = Get-Content $filepath -Encoding byte;
    # Using ReadAllBytes method as the Get-Content used alot of memory on the machine
    $fileStream = [System.IO.File]::OpenRead($filepath)
    $chunks = [Math]::Ceiling($fileStream.Length / $chunkSizeInBytes)

    # Upload each chunk.
    $ids = @()
    $cc = 1
    $chunk = 0

    # Start the timer for SAS URI renewal
    $SASRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()


    while ($fileStream.Position -lt $fileStream.Length) {
        $id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000")))
        $ids += $id

        $size = [Math]::Min($chunkSizeInBytes, $fileStream.Length - $fileStream.Position)
        $body = New-Object byte[] $size
        $fileStream.Read($body, 0, $size) > $null
        $totalBytes += $size

        Write-Progress -Activity "Uploading File to Azure Storage" -Status "Uploading chunk $cc of $chunks" -PercentComplete ($cc / $chunks * 100)
        $cc++

        UploadAzureStorageChunk $sasUri $id $body | Out-Null
        $chunk++

        # Ensure the SAS Uri is renewed
        if ($SASRenewalTimer.ElapsedMilliseconds -ge 450000) {
            Write-Host -Message "SAS Uri renewal is required, attempting to renew"
            $RenewSASURIRequest = Invoke-MgGraphRequest -uri "$($fileUri)/renewUpload" -Method "POST" -Body "{}"
            $Stage = "AzureStorageUriRenewal"
            do {
                $GraphRequest = Invoke-MgGraphRequest -uri $fileUri -Method "GET"
                switch ($GraphRequest.uploadState) {
                    "$($Stage)Pending" {
                        Write-Host -Message "Intune service request for operation '$($Stage)' is in pending state, sleeping for 10 seconds"
                        Start-Sleep -Seconds 10
                    }
                    "$($Stage)Failed" {
                        Write-Host -Message "Intune service request for operation '$($Stage)' failed"
                        throw
                    }
                    "$($Stage)TimedOut" {
                        Write-Host -Message "Intune service request for operation '$($Stage)' timed out"
                        throw
                    }
                }
            }
            until ($GraphRequest.uploadState -like "$($Stage)Success")
            Write-Host -Message "Intune service request for operation '$($Stage)' was successful with uploadState: $($GraphRequest.uploadState)"

            $SASRenewalTimer.Restart()
        }
    }

    # Stop timer
    $SASRenewalTimer.Stop()

    $fileStream.Close()
    Write-Progress -Completed -Activity "Uploading File to Azure Storage"

    # Finalize the upload.
    FinalizeAzureStorageUpload $sasUri $ids | Out-Null
}

Trouble #3

When I enabled SAS update, the upload went smoothly, probably because updates are made regularly like this.

Image description

However, it failed at the very end. What is a 400 error?

Image description

This was the only thing I didn't really understand, but I tried putting in a sleep of 180 seconds (3 minutes) before the final update, and it actually worked.


        # Commit the file to the service
        Invoke-MgCommitDeviceAppManagementMobileAppMicrosoftGraphWin32LobAppContentVersionFile -MobileAppId $mobileAppId -MobileAppContentId $ContentVersionId -MobileAppContentFileId $ContentVersionFileId -BodyParameter $params

        # Wait for the file to be processed
        Write-Host "Waiting for the file to be processed..." -ForegroundColor Yellow
        $file = WaitForFileProcessing $fileUri "CommitFile"

        Start-Sleep -Seconds 180

        # Check if we are only updating the content
        if ($UpdateAppContentOnly) {
            $params = @{
                "@odata.type"           = "#microsoft.graph.win32LobApp"
                committedContentVersion = "$ContentVersionId"
            }
        }
        else {
            $params = $mobileAppBody
            $params.committedContentVersion = "$ContentVersionId"
        }

        $params = $params | ConvertTo-Json

        # Update the application with the new content version
        Write-Host "Updating the application with the new content version..." -ForegroundColor Yellow
        Update-MgDeviceAppManagementMobileApp -MobileAppId $mobileAppId -BodyParameter $params

I finally got it to work properly, but it's taking a long time to upload.

It seems that MS's sample didn't take into account uploads of such large sizes.

The updated file is here. I've also submitted a pull request.

https://github.com/keitanak/mggraph-intune-samples/blob/LargeUpload/LOB_Application/Win32_Application_Update.ps1