######################################################## # # Common module functions # ######################################################## function Add-ModuleMenuItems { Add-MenuItem (New-Object PSObject -Property @{ Title = (Get-ApplicationName) MenuID = "IntuneGraphAPI" Script = [ScriptBlock]{Get-Applications} }) } function Get-SupportedImportObjects { $global:importObjects += (New-Object PSObject -Property @{ Selected = $true Title = (Get-ApplicationName) Script = [ScriptBlock]{ param($rootFolder) Write-Status "Import all applications" Import-AllApplicationObjects (Join-Path $rootFolder (Get-ApplicationFolderName)) } }) } function Get-SupportedExportObjects { $global:exportObjects += (New-Object PSObject -Property @{ Selected = $true Title = (Get-ApplicationName) Script = [ScriptBlock]{ param($rootFolder) Write-Status "Export all applications" Get-ApplicationObjects | ForEach-Object { Export-SingleApplication $PSItem.Object (Join-Path $rootFolder (Get-ApplicationFolderName)) } } }) } function Export-AllObjects { param($addObjectSubfolder) $subFolder = "" if($addObjectSubfolder) { $subFolder = Get-ApplicationFolderName } } ######################################################## # # Object specific functions # ######################################################## function Get-ApplicationName { (Get-ApplicationFolderName) } function Get-ApplicationFolderName { "Applications" } function Get-Applications { Write-Status "Loading applications" $dgObjects.ItemsSource = @(Get-ApplicationObjects) #Scriptblocks that will perform the export tasks. empty by default $script:exportParams = @{} $script:exportParams.Add("ExportAllScript", [ScriptBlock]{ Export-AllApplications $global:txtExportPath.Text Set-ObjectGrid Write-Status "" }) $script:exportParams.Add("ExportSelectedScript", [ScriptBlock]{ Export-SelectedApplication $global:txtExportPath.Text Set-ObjectGrid Write-Status "" }) #Scriptblock that will perform the import all files $script:importAll = [ScriptBlock]{ Import-AllApplicationObjects $global:txtImportPath.Text Set-ObjectGrid } #Scriptblock that will perform the import of selected files $script:importSelected = [ScriptBlock]{ Import-ApplicationObjects $global:lstFiles.ItemsSource -Selected Set-ObjectGrid } #Scriptblock that will read json files $script:getImportFiles = [ScriptBlock]{ Show-FileListBox $global:lstFiles.ItemsSource = @(Get-JsonFileObjects $global:txtImportPath.Text -Exclude "*_Settings.json") } $importExtension = (New-Object PSObject -Property @{ Xaml = @" "@ Script = [ScriptBlock]{ param($form) $script:txtPackagePath = $form.FindName("txtPackagePath") $btnBrowsePackagePath = $form.FindName("btnBrowsePackagePath") $script:txtPackagePath.Text = Get-SettingValue "IntuneAppPackages" $btnBrowsePackagePath.Tag = $script:txtPackagePath $btnBrowsePackagePath.Add_Click({ $folder = Get-Folder $this.Tag.Text if($folder) { $this.Tag.Text = $folder } }) } }) $script:importParams = @{} $script:importParams.Add("Extension", $importExtension) Add-DefaultObjectButtons -export ([scriptblock]{Show-DefaultExportGrid @script:exportParams}) -import ([scriptblock]{Show-DefaultImportGrid -ImportAll $script:importAll -ImportSelected $script:importSelected -GetFiles $script:getImportFiles @script:importParams}) -ViewFullObject ([scriptblock]{Get-ApplicationObject $global:dgObjects.SelectedItem.Object}) } function Get-ApplicationObjects { Get-GraphObjects -Url "/deviceAppManagement/mobileApps?`$filter=(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&`$orderby=displayName" } function Get-ApplicationObject { param($object, $additional = "") if(-not $Object.id) { return } Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($Object.id)$additional" } function Export-AllApplications { param($path = "$env:Temp") if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null } if(Test-Path $path) { foreach($objTmp in ($global:dgObjects.ItemsSource)) { Export-SingleApplication $objTmp.Object $path } } } function Export-SelectedApplication { param($path = "$env:Temp") Export-SingleApplication $global:dgObjects.SelectedItem.Object $path } function Export-SingleApplication { param($psObj, $path = "$env:Temp") if(-not $psObj) { return } if($global:runningBulkExport -ne $true) { if($global:chkAddCompanyName.IsChecked) { $path = Join-Path $path $global:organization.displayName } if($global:chkAddObjectType.IsChecked) { $path = Join-Path $path (Get-ApplicationFolderName) } } if(-not (Test-Path $path)) { mkdir -Path $path -Force -ErrorAction SilentlyContinue | Out-Null } if(Test-Path $path) { Write-Status "Export $($psObj.displayName)" $obj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($psObj.id)?`$expand=assignments" if($obj) { $fileName = "$path\$((Remove-InvalidFileNameChars $obj.displayName)).json" ConvertTo-Json $obj -Depth 5 | Out-File $fileName -Force Add-MigrationInfo $obj.assignments } $global:exportedObjects++ } } function Copy-Application { if(-not $dgObjects.SelectedItem) { [System.Windows.MessageBox]::Show("No object selected`n`nSelect application item you want to copy", "Error", "OK", "Error") return } $ret = Show-InputDialog "Copy application" "Select name for the new object" "$($dgObjects.SelectedItem.displayName) - Copy" if($ret) { # Export profile Write-Status "Export $($dgObjects.SelectedItem.displayName)" # Convert to Json and back to clone the object $obj = ConvertTo-Json $dgObjects.SelectedItem.Object -Depth 5 | ConvertFrom-Json if($obj) { # Import new profile $obj.displayName = $ret Import-Application $obj | Out-Null $dgObjects.ItemsSource = @(Get-ApplicationObjects) } Write-Status "" } $dgObjects.Focus() } function Import-Application { param($obj) Start-PreImport $obj -RemoveProperties @("uploadState","publishingState","isAssigned","roleScopeTagIds","dependentAppCount","committedContentVersion","id","isFeatured","size") Write-Status "Import $($obj.displayName)" Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps" -Content (ConvertTo-Json $obj -Depth 5) -HttpMethod POST } function Import-AllApplicationObjects { param($path = "$env:Temp") Import-ApplicationObjects (Get-JsonFileObjects $path) } function Import-ApplicationObjects { param( $Objects, [switch] $Selected ) Write-Status "Import applications" foreach($obj in $objects) { if($Selected -and $obj.Selected -ne $true) { continue } if($global:runningBulkImport) { $pkgPath = Get-SettingValue "IntuneAppPackages" } else { $pkgPath = $script:txtPackagePath.Text } $appFile = "$($pkgPath)\$($obj.Object.fileName)" if(Test-Path $appFile) { Write-Log "Import Application: $($obj.Object.displayName) ($($obj.Object."@odata.type"))" $assignments = Get-GraphAssignmentsObject $obj.Object ($obj.FileInfo.DirectoryName + "\" + $obj.FileInfo.BaseName + "_assignments.json") $response = Import-Application $obj.Object if($response) { $global:importedObjects++ Copy-AppPackageToIntune $appFile $response Import-GraphAssignments $assignments "mobileAppAssignments" "/deviceAppManagement/mobileApps/$($response.Id)/assign" "#microsoft.graph.mobileAppAssignment" } } else { Write-Log "Application file $appFile not found. Skipping app $($obj.Object.displayName)" 3 } } $dgObjects.ItemsSource = @(Get-ApplicationObjects) Write-Status "" } function Start-DownloadAppContent { param($obj, $path) # Not use but kept for reference. File can be download but it will be encrypted $appId = $obj.Id $appId = "b2b79110-31f7-40bd-923b-228415c92cdb" $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 $contentFiles = Invoke-GraphRequest "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files" foreach($tmpFile in $contentFiles) { $contentFile = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($tmpFile.Id)" $downloadUrl = $contentFile.azureStorageUri } } function Copy-AppPackageToIntune { param($packageFile, $appObj) $appType = $appObj.'@odata.type'.Trim('#') if($appType -eq "microsoft.graph.win32LobApp") { Copy-Win32LOBPackage $packageFile $appObj } elseif($appType -eq "microsoft.graph.windowsMobileMSI") { Copy-MSILOB $packageFile $appObj } elseif($appType -eq "microsoft.graph.iosLOBApp") { Copy-iOSLOB $packageFile $appObj } elseif($appType -eq "microsoft.graph.androidLOBApp") { Copy-AndroidLOB $packageFile $appObj } } ######################################################################################### # # 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 = @{} 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") if(-not $msiInfo) { return } $fileEncryptionInfo = New-IntuneEncryptedFile $msiFile $tmpFile [xml]$manifestXML = '' $manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["ProductCode"] $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)) } Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody Remove-Item $tmpFile -Force } 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 } 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 } 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 # 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 + "\" + $DetectionXML.ApplicationInfo.FileName # 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 = $DetectionXML.ApplicationInfo.FileName 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 } function Add-FileToIntuneApp { param($appId, $appType, $appFile, $fileBody) $contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions" $contentVersionId = $contentVersion.value[0].id $fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5) if(-not $fileObj) { return } # Wait for Azure storage URI $fileObj = Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "AzureStorageUriRequest" if(-not $fileObj) { return } # Upload file Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" # Commit the file $reponse = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)/commit" -HttpMethod POST -Content (ConvertTo-Json $fileEncryptionInfo -Depth 5) Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile" # Commit the content version $commitAppBody = @{ "@odata.type" = "#$appType" committedContentVersion = $contentVersionId } $reponse = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5) } 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 -s 5 } 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($chunkSizeInBytes, $fileSize - $start) $bytes = $reader.ReadBytes($length) $currentChunk = $chunk + 1 Write-Status "Uploading file to Azure Storage`n`nUploading chunk $currentChunk of $chunks ($(($currentChunk / $chunks*100))%)" Write-AzureStorageChunk $sasUri $id $bytes if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000) { Request-RenewAzureStorageUpload $fileUri $sasRenewalTimer.Restart() } } $reader.Close() } finally { if ($reader -ne $null) { $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 += '' try { Invoke-RestMethod $uri -Method Put -Body $xml } 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" } try { $response = Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody } catch { Write-Log "Failed to upload file chunk. $($_.Exception.Message)" 3 } } 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 }