#
.SYNOPSIS
Module for Intune Applications
.DESCRIPTION
This module manages Application objects in Intune e.g. uploading application files
.NOTES
Author: Mikael Karlsson
#>
function Get-ModuleVersion
{
'3.9.6'
}
#########################################################################################
#
# Upload file functions are based on the following scripts
# https://github.com/microsoftgraph/powershell-intune-samples/tree/master/LOB_Application
#
#########################################################################################
function Export-IntunewinFileObject
{
param($intunewinFile, $objectName, $toFile)
Add-Type -Assembly System.IO.Compression.FileSystem
$zip = [IO.Compression.ZipFile]::OpenRead($intunewinFile)
$zip.Entries | where { $_.Name -like $objectName } | foreach {
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $toFile, $true)
}
$zip.Dispose()
}
function Get-MSIFileInformation
{
param($MSIFile, $Properties)
$values = @{}
if(-not $MSIFile) { return }
$fi = [IO.FileInfo]$MSIFile
if($fi.Extension -ne ".msi") { return }
try
{
$wiObj = New-Object -ComObject WindowsInstaller.Installer
$MSIDb = $wiObj.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $wiObj, @($MSIFile, 0))
foreach($prop in $Properties)
{
$Query = "SELECT Value FROM Property WHERE Property = '$($prop)'"
$View = $MSIDb.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDb, ($Query))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null) | Out-Null
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
$values.Add($prop, $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1).ToString().Trim())
}
$MSIDb.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDb, $null) | Out-Null
$View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null) | Out-Null
$MSIDb = $null
$View = $null
}
catch
{
Write-Log "Failed to get MSI info from $MSIFile. $($_.Exception.Message)" 3
}
finally
{
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wiObj) | Out-Null
[System.GC]::Collect() | Out-Null
}
$values
}
function Copy-MSILOB
{
param($msiFile, $appObj)
if(-not $msiFile -or (Test-Path $msiFile) -eq $false)
{
return
}
$appId = $appObj.Id
$appType = $appObj.'@odata.type'.Trim('#')
$tmpFile = [IO.Path]::GetTempFileName()
$msiInfo = Get-MSIFileInformation $msiFile @("ProductName", "ProductCode", "ProductVersion", "ProductLanguage", "UpgradeCode", "ALLUSERS")
if(-not $msiInfo) { return }
$fileEncryptionInfo = New-IntuneEncryptedFile $msiFile $tmpFile
[xml]$manifestXML = ''
$manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["UpgradeCode"]
if($msiInfo["ALLUSERS"] -eq 1)
{
$manifestXML.MobileMsiData.MsiExecutionContext = "System"
}
$appFileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = [IO.Path]::GetFileName($msiFile)
size = (Get-Item $msiFile).Length
sizeEncrypted = (Get-Item $tmpFile).Length
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
isDependency = $false
}
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-MSIXLOB
{
param($msixFile, $appObj)
if(-not $msixFile -or (Test-Path $msixFile) -eq $false)
{
return
}
$fi = [IO.FileInfo]$msixFile
$appId = $appObj.Id
$appType = $appObj.'@odata.type'.Trim('#')
$tmpFile = [IO.Path]::GetTempFileName()
$fileEncryptionInfo = New-IntuneEncryptedFile $msixFile $tmpFile
$manifest = $fi.Name
$appFileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = [IO.Path]::GetFileName($msixFile)
size = (Get-Item $msixFile).Length
sizeEncrypted = (Get-Item $tmpFile).Length
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifest))
isDependency = $false
}
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-iOSLOB
{
param($pkgFile, $appObj)
if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
{
return
}
$appId = $appObj.Id
$appType = $appObj.'@odata.type'.Trim('#')
$tmpFile = [IO.Path]::GetTempFileName()
$fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
[string]$manifestStr = 'itemsassetskindsoftware-packageurl{UrlPlaceHolder}metadataAppRestrictionPolicyTemplate http://management.microsoft.com/PolicyTemplates/AppRestrictions/iOS/v1AppRestrictionTechnologyWindows Intune Application Restrictions Technology for iOSIntuneMAMVersionCFBundleSupportedPlatformsiPhoneOSMinimumOSVersion9.0bundle-identifierbundleidbundle-versionbundleversionkindsoftwaresubtitleLaunchMeSubtitletitlebundletitle'
$manifestStr = $manifestStr.replace("bundleid", $appObj.bundleId)
$manifestStr = $manifestStr.replace("bundleversion",$appObj.identityVersion)
$manifestStr = $manifestStr.replace("bundletitle",$appObj.$displayName)
$appFileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = [IO.Path]::GetFileName($pkgFile)
size = (Get-Item $pkgFile).Length
sizeEncrypted = (Get-Item $tmpFile).Length
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestStr))
}
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-AndroidLOB
{
param($pkgFile, $appObj)
if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
{
return
}
$appId = $appObj.Id
$appType = $appObj.'@odata.type'.Trim('#')
$tmpFile = [IO.Path]::GetTempFileName()
$fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
[xml]$manifestXML = 'com.leadapps.android.radio.ncp101.0.5.4A_Online_Radio_1.0.5.4.apk3'
$manifestXML.AndroidManifestProperties.Package = $appObj.identityName
$manifestXML.AndroidManifestProperties.PackageVersionCode = $appObj.versionCode
$manifestXML.AndroidManifestProperties.PackageVersionName = $appObj.versionName
$manifestXML.AndroidManifestProperties.ApplicationName = [IO.Path]::GetFileName($pkgFile)
$appFileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = [IO.Path]::GetFileName($pkgFile)
size = (Get-Item $pkgFile).Length
sizeEncrypted = (Get-Item $tmpFile).Length
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
}
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-Win32LOBPackage
{
param($intunewinFile, $appObj)
if(-not $intunewinFile -or (Test-Path $intunewinFile) -eq $false)
{
return
}
$appId = $appObj.Id
$appType = $appObj.'@odata.type'.Trim('#')
#Extract the detection.xml from the intunewin file
$tmpFile = [IO.Path]::GetTempFileName()
Export-IntunewinFileObject $intunewinFile "detection.xml" $tmpFile
[xml]$DetectionXML = Get-Content $tmpFile
Remove-Item -Path $tmpFile
$fi = [IO.FileInfo]$intunewinFile
# Get encryption info from detection.xml and build encryptionInfo object
$encryptionInfo = @{}
$encryptionInfo.encryptionKey = $DetectionXML.ApplicationInfo.EncryptionInfo.EncryptionKey
$encryptionInfo.macKey = $DetectionXML.ApplicationInfo.EncryptionInfo.macKey
$encryptionInfo.initializationVector = $DetectionXML.ApplicationInfo.EncryptionInfo.initializationVector
$encryptionInfo.mac = $DetectionXML.ApplicationInfo.EncryptionInfo.mac
$encryptionInfo.profileIdentifier = "ProfileVersion1"
$encryptionInfo.fileDigest = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigest
$encryptionInfo.fileDigestAlgorithm = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigestAlgorithm
$tmpIntunewinPath = ([IO.Path]::GetTempPath() + [Guid]::NewGuid().ToString("n"))
mkdir $tmpIntunewinPath | Out-Null
$tmpIntunewinFile = $tmpIntunewinPath + "\" + $fi.Name
# Extract the encrypted file from the intunewin file
Export-IntunewinFileObject $intunewinFile $DetectionXML.ApplicationInfo.FileName $tmpIntunewinFile
# Create mobileAppContentFile object for the file
$fileEncryptionInfo = @{}
$fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
$fileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = "IntunePackage.intunewin"
size = [int64]$DetectionXML.ApplicationInfo.UnencryptedContentSize
sizeEncrypted = (Get-Item $tmpIntunewinFile).Length
manifest = $null
isDependency = $false
}
Add-FileToIntuneApp $appId $appType $tmpIntunewinFile $fileBody
# Remove extracted inintunewin file
Remove-Item $tmpIntunewinPath -Force -Recurse
$fileEncryptionInfo
}
function Add-FileToIntuneApp
{
param($appId, $appType, $appFile, $fileBody)
$contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions" -HttpMethod POST -Content "{}" -ODataMetadata "Minimal"
$contentVersionId = $contentVersion.id
$fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5) -ODataMetadata "Minimal"
if(-not $fileObj)
{
return
}
Write-Log "File object created. ID: $($fileObj.id)"
# Wait for Azure storage URI
$fileObj = Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "AzureStorageUriRequest"
if(-not $fileObj)
{
Write-Log "No File Object returned from commit. Upload failed" 3
return
}
# Upload file
Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" | Out-Null
# Commit the file
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)/commit" -HttpMethod POST -Content (ConvertTo-Json $fileEncryptionInfo -Depth 5) | Out-Null
Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile" | Out-Null
# Commit the content version
$commitAppBody = @{
"@odata.type" = "#$appType"
committedContentVersion = $contentVersionId
}
# if($fileBody.Name) {
# $fileUploadName = $fileBody.Name
# }
# else {
$fiUpload = [IO.FileInfo]$appFile
$fileUploadName = $fiUpload.Name
# }
$commitAppBody.Add("fileName",$fileUploadName)
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5) | Out-Null
Write-Log "Upload finished for file $fileUploadName version $contentVersionId"
}
function Wait-IntuneFileState
{
param($fileUri, $state, $maxWait = 60)
Write-Status "Wait for state $state"
$endWait = (Get-Date).AddMinutes($maxWait)
$successState = "$($state)Success"
$pendingState = "$($state)Pending"
$failedState = "$($state)Failed"
$timedOutState = "$($state)TimedOut"
$file = $null
$succes = $false
while ((Get-Date) -lt $endWait)
{
$file = Invoke-GraphRequest -Url $fileUri
if ($file.uploadState -eq $successState)
{
$succes = $true
break
}
elseif ($file.uploadState -ne $pendingState)
{
Write-Log "Failed to upload file. State: $($file.uploadState)" 3
return
}
Start-Sleep -Seconds 1
}
if($succes -eq $false)
{
Write-Log "Wait for state operation timed out" 3
return
}
$file
}
function Send-IntuneFileToAzureStorage
{
param($sasUri, $filepath, $fileUri)
try
{
$chunkSizeInBytes = 5MB
# Start the timer for SAS URI renewal.
$sasRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()
# Find the file size and open the file.
$fileSize = (Get-Item $filepath).length
$chunks = [Math]::Ceiling($fileSize / $chunkSizeInBytes)
$reader = New-Object System.IO.BinaryReader([System.IO.File]::Open($filepath, [System.IO.FileMode]::Open))
$position = $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin)
# Upload each chunk. Check whether a SAS URI renewal is required after each chunk is uploaded and renew if needed.
$ids = @()
for ($chunk = 0; $chunk -lt $chunks; $chunk++)
{
$id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000")))
$ids += $id
$start = $chunk * $chunkSizeInBytes
$length = [Math]::Min([uint64]($chunkSizeInBytes), [uint64]($fileSize - $start))
$bytes = $reader.ReadBytes($length)
$currentChunk = $chunk + 1
Write-Status "Uploading file to Azure Storage`n`nUploading chunk $currentChunk of $chunks ($(("{0:N2}" -f ($currentChunk / $chunks*100)))%)"
if((Write-AzureStorageChunk $sasUri $id $bytes) -eq $false)
{
Write-Log "Upload failed. Abourting..." 3
break
}
if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000)
{
Request-RenewAzureStorageUpload $fileUri
$sasRenewalTimer.Restart()
}
}
}
catch
{
Write-Log "Failed to send file to Intune. $($_.Exception.Message)" 3
}
finally
{
if ($reader -ne $null)
{
$reader.Close()
$reader.Dispose()
}
}
# Finalize the upload.
$uploadResponse = Set-FinalizeAzureStorageUpload $sasUri $ids
}
function Request-RenewAzureStorageUpload
{
param($fileUri)
$fileObj = Invoke-GraphRequest -Url "$fileUri/renewUpload" -HttpMethod POST
$file = Wait-IntuneFileState $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds
}
function Set-FinalizeAzureStorageUpload
{
param($sasUri, $ids)
$uri = "$sasUri&comp=blocklist"
if(($uri -notmatch "^http://|^https://"))
{
$uri = $global:graphURL + "/" + $uri.TrimStart('/')
}
$request = "PUT $uri"
$xml = ''
foreach ($id in $ids)
{
$xml += "$id"
}
$xml += ''
$params = @{}
$proxyURI = Get-ProxyURI
if($proxyURI)
{
$params.Add("proxy", $proxyURI)
$params.Add("UseBasicParsing", $true)
}
try
{
Invoke-RestMethod $uri -Method Put -Body $xml @params
}
catch
{
Write-Log "Failed to finilize upload. $($_.Exception.Message)" 3
}
}
function Write-AzureStorageChunk
{
param($sasUri, $id, $body)
$uri = "$sasUri&comp=block&blockid=$id"
if(($uri -notmatch "^http://|^https://"))
{
$uri = $global:graphURL + "/" + $uri.TrimStart('/')
}
$request = "PUT $uri"
$iso = [System.Text.Encoding]::GetEncoding("iso-8859-1")
$encodedBody = $iso.GetString($body)
$headers = @{
"x-ms-blob-type" = "BlockBlob"
"Content-Type" = "application/octet-stream"
}
$curProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
$success = $false
$retryCount = 0
$params = @{}
$proxyURI = Get-ProxyURI
if($proxyURI)
{
$params.Add("proxy", $proxyURI)
}
while($true)
{
try
{
$response = Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody -UseBasicParsing @params
if($retryCount -gt 0)
{
Write-Log "Chunk uploaded successfully"
}
$success = $true
break
}
catch
{
if($_.Exception.HResult -eq -2146233079 -and $retryCount -lt 6)
{
Write-Log "Failed to upload file chunk. Retry in 10 s" 2
$retryCount++
Start-Sleep -Seconds 10
}
else
{
Write-Log "Failed to upload file chunk. $($_.Exception.Message)" 3
break
}
}
}
$ProgressPreference = $curProgressPreference
$success
}
function Get-IntuneKey
{
try
{
$aes = [System.Security.Cryptography.Aes]::Create()
$aesProvider = New-Object System.Security.Cryptography.AesCryptoServiceProvider
$aesProvider.GenerateKey()
$aesProvider.Key
}
finally
{
if ($aesProvider -ne $null) { $aesProvider.Dispose() }
if ($aes -ne $null) { $aes.Dispose() }
}
}
function Get-IntuneKeyIV
{
try
{
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.IV
}
finally
{
if ($aes -ne $null) { $aes.Dispose() }
}
}
function Start-EncryptFileWithIV
{
param($sourceFile, $targetFile, $encryptionKey, $hmacKey, $initializationVector)
$bufferBlockSize = 1024 * 4
$computedMac = $null
try
{
$aes = [System.Security.Cryptography.Aes]::Create()
$hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
$hmacSha256.Key = $hmacKey
$hmacLength = $hmacSha256.HashSize / 8
$buffer = New-Object byte[] $bufferBlockSize
$bytesRead = 0
$targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
$targetStream.Write($buffer, 0, $hmacLength + $initializationVector.Length)
try
{
$encryptor = $aes.CreateEncryptor($encryptionKey, $initializationVector)
$sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
$cryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
$targetStream = $null
while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
{
$cryptoStream.Write($buffer, 0, $bytesRead)
$cryptoStream.Flush()
}
$cryptoStream.FlushFinalBlock()
}
finally
{
if ($cryptoStream -ne $null) { $cryptoStream.Dispose() }
if ($sourceStream -ne $null) { $sourceStream.Dispose() }
if ($encryptor -ne $null) { $encryptor.Dispose() }
}
try
{
$finalStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::Read)
$finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
$finalStream.Write($initializationVector, 0, $initializationVector.Length)
$finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
$hmac = $hmacSha256.ComputeHash($finalStream)
$computedMac = $hmac
$finalStream.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
$finalStream.Write($hmac, 0, $hmac.Length)
}
finally
{
if ($finalStream -ne $null) { $finalStream.Dispose() }
}
}
finally
{
if ($targetStream -ne $null) { $targetStream.Dispose() }
if ($aes -ne $null) { $aes.Dispose() }
}
$computedMac
}
function New-IntuneEncryptedFile
{
param($sourceFile, $targetFile)
$encryptionKey = Get-IntuneKey
$hmacKey = Get-IntuneKey
$initializationVector = Get-IntuneKeyIV
# Create the encrypted target file and compute the HMAC value.
$mac = Start-EncryptFileWithIV $sourceFile $targetFile $encryptionKey $hmacKey $initializationVector
# Compute the SHA256 hash of the source file and convert the result to bytes.
$fileDigest = (Get-FileHash $sourceFile -Algorithm SHA256).Hash
$fileDigestBytes = New-Object byte[] ($fileDigest.Length / 2)
for ($i = 0; $i -lt $fileDigest.Length; $i += 2)
{
$fileDigestBytes[$i / 2] = [System.Convert]::ToByte($fileDigest.Substring($i, 2), 16)
}
# Return an object that will serialize correctly to the file commit Graph API.
$encryptionInfo = @{}
$encryptionInfo.encryptionKey = [System.Convert]::ToBase64String($encryptionKey)
$encryptionInfo.macKey = [System.Convert]::ToBase64String($hmacKey)
$encryptionInfo.initializationVector = [System.Convert]::ToBase64String($initializationVector)
$encryptionInfo.mac = [System.Convert]::ToBase64String($mac)
$encryptionInfo.profileIdentifier = "ProfileVersion1"
$encryptionInfo.fileDigest = [System.Convert]::ToBase64String($fileDigestBytes)
$encryptionInfo.fileDigestAlgorithm = "SHA256"
$fileEncryptionInfo = @{}
$fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
$fileEncryptionInfo
}
function Start-DecryptFile
{
param($sourceFile, $targetFile, $encryptionKey, $initializationVector)
if([IO.File]::Exists($targetFile))
{
$fi = [IO.FileInfo]$targetFile
$newName = $fi.Name + "_$((Get-Date).ToString("yyyyMMdd_HHmm"))" + $fi.Extension
$targetFile = $fi.DirectoryName + "\$newName"
Write-Log "Target file exists. Changing target file to $targetFile" 2
}
$bufferBlockSize = 1024 * 4
try
{
$aes = [System.Security.Cryptography.Aes]::Create()
$buffer = New-Object byte[] $bufferBlockSize
$bytesRead = 0
$targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try
{
$sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None)
$decryptor = $aes.CreateDecryptor([Convert]::FromBase64String($encryptionKey), [Convert]::FromBase64String($initializationVector))
$decryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
$sourceStream.Seek(48L, [System.IO.SeekOrigin]::Begin)
while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
{
$decryptoStream.Write($buffer, 0, $bytesRead)
$decryptoStream.Flush()
}
$decryptoStream.FlushFinalBlock()
}
finally
{
if ($null -ne $decryptoStream) { $decryptoStream.Dispose() }
if ($null -ne $targetStream) { $targetStream.Dispose() }
if ($null -ne $decryptor) { $decryptor.Dispose() }
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
}
}
finally
{
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
if ($null -ne $aes) { $aes.Dispose() }
}
}
function Start-DownloadAppContent
{
param($obj, $destinationFile, [switch]$GetContentFileInfoOnly)
# Not use but kept for reference. File can be download but it will be encrypted
if([IO.File]::Exists($destinationFile))
{
try { [IO.File]::Delete($encryptionFile) }
catch {}
}
$appId = $obj.Id
$appInfo = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId"
$appType = $appInfo.'@odata.type'.Trim('#')
#$contentVersions = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions"
#$contentVerId = $contentVersions.Value[0].id
$contentVerId = $appInfo.committedContentVersion
$contentFiles = Invoke-GraphRequest "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files"
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($contentFiles.value[-1].Id)" -NoError
if(-not $contentFile)
{
foreach($file in $contentFiles.value)
{
if($contentFiles.value[-1].Id -eq $file.id) { continune }
# NOT happy about this. file objects are not always returned in the order of upload.
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($file.Id)" -NoError
if($contentFile)
{
break
}
}
}
if($contentFile.azureStorageUri)
{
if($GetContentFileInfoOnly -ne $true)
{
Start-DownloadFile $contentFile.azureStorageUri $destinationFile
}
return $contentFile
}
else
{
Write-Log "Could not find file object for app $($obj.displayName) ($($appId))" 2
}
}