Public/Expand-IntuneWin32AppPackage.ps1
function Expand-IntuneWin32AppPackage { <# .SYNOPSIS Decode an existing .intunewin file already packaged as a Win32 application and allow it's contents to be extracted. .DESCRIPTION Decode an existing .intunewin file already packaged as a Win32 application and allow it's contents to be extracted. .PARAMETER FilePath Specify the full path of the locally available packaged Win32 application, e.g. 'C:\Temp\AppName.intunewin'. .PARAMETER Force Specify parameter to overwrite existing files already in working directory. .NOTES Author: Nickolaj Andersen Contact: @NickolajA Created: 2020-01-04 Updated: 2020-06-10 Version history: 1.0.0 - (2020-01-04) Function created 1.0.1 - (2020-06-10) Amended the pattern validation for FilePath parameter to only require a single folder instead of two-level folder structure #> [CmdletBinding(SupportsShouldProcess = $true)] param( [parameter(Mandatory = $true, HelpMessage = "Specify the full path of the locally available packaged Win32 application, e.g. 'C:\Temp\AppName.intunewin'.")] [ValidateNotNullOrEmpty()] [ValidatePattern("^[A-Za-z]{1}:\\\w+")] [ValidateScript({ # Check if path contains any invalid characters if ((Split-Path -Path $_ -Leaf).IndexOfAny([IO.Path]::GetInvalidFileNameChars()) -ge 0) { Write-Warning -Message "$(Split-Path -Path $_ -Leaf) contains invalid characters"; break } else { # Check if file extension is intunewin if ([System.IO.Path]::GetExtension((Split-Path -Path $_ -Leaf)) -like ".intunewin") { return $true } else { Write-Warning -Message "$(Split-Path -Path $_ -Leaf) contains unsupported file extension. Supported extension is '.intunewin'"; break } } })] [string]$FilePath, [parameter(Mandatory = $false, HelpMessage = "Specify parameter to overwrite existing files already in working directory.")] [switch]$Force ) Begin { # Load System.IO.Compression assembly for managing compressed files try { $ClassImport = Add-Type -AssemblyName "System.IO.Compression.FileSystem" -ErrorAction Stop -Verbose:$false } catch [System.Exception] { Write-Warning -Message "An error occurred while loading System.IO.Compression.FileSystem assembly. Error message: $($_.Exception.Message)"; break } # Set script variable for error action preference $ErrorActionPreference = "Stop" } Process { if (Test-Path -Path $FilePath) { try { # Read Win32 app meta data Write-Verbose -Message "Attempting to gather required Win32 app meta data from file: $($FilePath)" $IntuneWinMetaData = Get-IntuneWin32AppMetaData -FilePath $FilePath -ErrorAction Stop if ($IntuneWinMetaData -ne $null) { # Retrieve Base64 encoded encryption key $Base64Key = $IntuneWinMetaData.ApplicationInfo.EncryptionInfo.EncryptionKey Write-Verbose -Message "Found Base64 encoded encryption key from meta data: $($Base64Key)" # Retrieve Base64 encoded initialization vector $Base64IV = $IntuneWinMetaData.ApplicationInfo.EncryptionInfo.InitializationVector Write-Verbose -Message "Found Base64 encoded initialization vector from meta data: $($Base64IV)" try { # Extract encoded .intunewin from Contents folder Write-Verbose -Message "Attempting to extract encoded .intunewin file from inside Contents folder of the Win32 application package" $ExtractedIntuneWinFile = $FilePath + ".extracted" $ZipFile = [System.IO.Compression.ZipFile]::OpenRead($FilePath) #$IntuneWinFileName = Split-Path -Path $FilePath -Leaf $IntuneWinFileName = $IntuneWinMetaData.ApplicationInfo.FileName $ZipFile.Entries | Where-Object { $_.Name -like $IntuneWinFileName } | ForEach-Object { [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $ExtractedIntuneWinFile, $true) } # Dispose of ZipFile from memory Write-Verbose -Message "Successfully extracted encoded .intunewin file from Contents folder, disposing ZipFile object" $ZipFile.Dispose() try { # Convert Base64 encryption info to bytes Write-Verbose -Message "Attempting to convert Base64 encoded encryption key and initialization vector secure strings" $Key = [System.Convert]::FromBase64String($Base64Key) $IV = [System.Convert]::FromBase64String($Base64IV) try { # Open target filestream for read/write $TargetFilePath = $FilePath + ".decoded" $TargetFilePathName = Split-Path -Path $TargetFilePath -Leaf if (Test-Path -Path $TargetFilePath) { if ($PSBoundParameters["Force"]) { try { Remove-Item -Path $TargetFilePath -Force -ErrorAction Stop } catch [System.Exception] { Write-Warning -Message "An error occurred while removing existing decoded file: $($TargetFilePathName). Error message: $($_.Exception.Message)"; break } } else { Write-Warning -Message "Existing file '$($TargetFilePathName)' already exists, use Force parameter to overwrite"; break } } Write-Verbose -Message "Attempting to create a new decoded .intunewin file: $($TargetFilePath)" [System.IO.FileStream]$FileStreamTarget = [System.IO.File]::Open($TargetFilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) try { # Create AES decryptor Write-Verbose -Message "Attempting to construct new AES decryptor with encryption key and initialization vector" $AES = [System.Security.Cryptography.Aes]::Create() [System.Security.Cryptography.ICryptoTransform]$Decryptor = $AES.CreateDecryptor($Key, $IV) try { # Open source filestream for read-only Write-Verbose -Message "Attepmting to open extracted .intunewin file: $($ExtractedIntuneWinFile)" [System.IO.FileStream]$FileStreamSource = [System.IO.File]::Open($ExtractedIntuneWinFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None) $FileStreamSourceSeek = $FileStreamSource.Seek(48l, [System.IO.SeekOrigin]::Begin) try { # Construct new CryptoStream Write-Verbose -Message "Attempting to create CryptoStream and write decoded chunks of data to file: $($TargetFilePath)" [System.Security.Cryptography.CryptoStream]$CryptoStream = New-Object -TypeName System.Security.Cryptography.CryptoStream -ArgumentList @($FileStreamTarget, $Decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write) -ErrorAction Stop # Write all chunks of data to decoded target file $buffer = New-Object byte[](2097152) while ($BytesRead = $FileStreamSource.Read($buffer, 0, 2097152)) { $CryptoStream.Write($buffer, 0, $BytesRead) $CryptoStream.Flush() } # Flush final block in cryptostream $CryptoStream.FlushFinalBlock() Write-Verbose -Message "Successfully decoded '$($IntuneWinFileName)' Win32 app package file to: $($TargetFilePath)" } catch [System.Exception] { Write-Warning -Message "An error occurred while creating a CryptoStream and writing decoded chunks of data to file: $($TargetFilePath). Error message: $($_.Exception.Message)" } } catch [System.Exception] { Write-Warning -Message "An error occurred while opening extracted .intunewin file '$($ExtractedIntuneWinFile)'. Error message: $($_.Exception.Message)" } } catch [System.Exception] { Write-Warning -Message "An error occurred while creating AES decryptor. Error message: $($_.Exception.Message)" } } catch [System.Exception] { Write-Warning -Message "An error occurred while creating a new decoded .intunewin file: $($TargetFilePath). Error message: $($_.Exception.Message)" } } catch [System.Exception] { Write-Warning -Message "An error occurred while converting Base64 encoded encryption key and initialization vector secure strings. Error message: $($_.Exception.Message)" } } catch [System.Exception] { Write-Warning -Message "An error occurred while extracing encoded .intunewin file from inside Contents folder of the Win32 application package. Error message: $($_.Exception.Message)" } } } catch [System.Exception] { Write-Warning -Message "An error occurred while gathering Win32 app meta data. Error message: $($_.Exception.Message)" } } else { Write-Warning -Message "Unable to locate specified .intunewin file" } } End { # Dispose of objects and release locks if ($CryptoStream -ne $null) { $CryptoStream.Dispose() } if ($FileStreamSource -ne $null) { $FileStreamSource.Dispose() } if ($Decryptor -ne $null) { $Decryptor.Dispose() } if ($FileStreamTarget -ne $null) { $FileStreamTarget.Dispose() } if ($AES -ne $null) { $AES.Dispose() } # Remove extracted intunewin file if (Test-Path -Path $ExtractedIntuneWinFile) { Remove-Item -Path $ExtractedIntuneWinFile -Force } } } |