diff --git a/CloudAPIPowerShellManagement.psd1 b/CloudAPIPowerShellManagement.psd1 index 906e02e..91d7a57 100644 --- a/CloudAPIPowerShellManagement.psd1 +++ b/CloudAPIPowerShellManagement.psd1 @@ -12,7 +12,7 @@ RootModule = 'CloudAPIPowerShellManagement.psm1' # Version number of this module. -ModuleVersion = '3.9.2' +ModuleVersion = '3.9.3' # Supported PSEditions # CompatiblePSEditions = @() @@ -69,7 +69,7 @@ Description = 'Management of Intune and Azure via Cloud APIs like Microsoft Grap NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = @("Initialize-CloudAPIManagement","Initialize-CloudAPIManagement") +FunctionsToExport = @("Initialize-CloudAPIManagement") # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. # CmdletsToExport = @() diff --git a/Core.psm1 b/Core.psm1 index aa975ad..ad069b3 100644 --- a/Core.psm1 +++ b/Core.psm1 @@ -11,7 +11,7 @@ This module handles the WPF UI function Get-ModuleVersion { - '3.9.2' + '3.9.3' } function Initialize-Window @@ -2069,7 +2069,7 @@ function Add-SettingsObject $section.Values += $obj } catch { } -} +} function Save-AllSettings { @@ -2797,7 +2797,17 @@ function Start-DownloadFile try { - Write-Status "Download file: `n$sourceURL" + $title = $sourceURL.Split("/")[-1] + $title = $title.Split("/")[0] + } + catch + { + $title = $sourceURL + } + + try + { + Write-Status "Download file: `n$title" $wc.DownloadFile($sourceURL, $targetFile) Write-Log "File downloaded to $targetFile" } @@ -2831,6 +2841,25 @@ function Get-ASCIIBytes $bytes } +function Get-DataGridValues +{ + param($dataGrid) + + $dgColumns = $dataGrid.Columns + + $properties = @() + + foreach($tmpCol in $dgColumns) + { + if(-not $tmpCol.Binding.Path.Path) { continue } + $propName = $tmpCol.Binding.Path.Path + $properties += @{n=$tmpCol.Header;e=([Scriptblock]::Create("`$_.$propName"))} + } + + ($dataGrid.ItemsSource | Select -Property $properties) +} + + New-Alias -Name ?? -value Invoke-Coalesce New-Alias -Name ?: -value Invoke-IfTrue Export-ModuleMember -alias * -function * \ No newline at end of file diff --git a/Extensions/Documentation.psm1 b/Extensions/Documentation.psm1 index a7b03df..3c01aae 100644 --- a/Extensions/Documentation.psm1 +++ b/Extensions/Documentation.psm1 @@ -20,7 +20,7 @@ $global:documentationProviders = @() function Get-ModuleVersion { - '2.0.2' + '2.0.3' } function Invoke-InitializeModule @@ -228,6 +228,7 @@ function Get-ObjectDocumentation $script:applicabilityRules = @() $script:objectAssignments = @() $script:objectScripts = @() + $script:admxCategories = $null $script:ObjectTypeFullTable = @{} # Hash table with objects that should be documented in a single table eg ScopeTags @@ -384,6 +385,7 @@ function Invoke-ObjectDocumentation $global:catRecommendedSettings = $null $global:intentCategoryDefs = $null $global:cfgCategories = $null + $script:admxCategories = $null $script:DocumentationLanguage = "en" $script:objectSeparator = [System.Environment]::NewLine @@ -879,7 +881,8 @@ function Invoke-TranslateADMXObject { if(-not $definitionValue.definition -and $definitionValues.'definition@odata.bind') { - $definition = Invoke-GraphRequest -Url $definitionValue.'definition@odata.bind' -ODataMetadata "minimal" @params + $url = $definitionValue.'definition@odata.bind' -replace $global:graphURL, ("https://$((?? $global:MSALGraphEnvironment "graph.microsoft.com"))/beta") + $definition = Invoke-GraphRequest -Url $url -ODataMetadata "minimal" @params if($definition) { $definitionValue | Add-Member -MemberType NoteProperty -Name "definition" -Value $definition @@ -1924,7 +1927,8 @@ function Get-LanguageString if(-not $script:languageStrings) { - $fileContent = Get-Content ($global:AppRootFolder + "\Documentation\Strings-$($script:DocumentationLanguage).json") -Encoding UTF8 + $lng = ?? $script:DocumentationLanguage "en" + $fileContent = Get-Content ($global:AppRootFolder + "\Documentation\Strings-$($lng).json") -Encoding UTF8 $script:languageStrings = $fileContent | ConvertFrom-Json } @@ -4432,7 +4436,7 @@ function local:Invoke-StartDocumentatiom # Add each object to the documentation foreach($curGroupId in ($sourceList.ObjectType | Select GroupID -Unique).GroupID) { - # New object group e.g. Script, Tennant, Device Configuration + # New object group e.g. Script, Tenant, Device Configuration # A group matches a menu item in the protal but can contain multiple object types if($global:cbDocumentationType.SelectedItem.NewObjectGroup) { @@ -5040,4 +5044,19 @@ function Set-TableObjects { $script:ObjectTypeFullTable.Add($objectInfo.ObjectType.Id, $objectInfo) } +} + +function Get-PolicyTypeName +{ + param($type, $default = $null) + + $categoryObj = Get-TranslationFiles $type + + if($null -eq $categoryObj) { return $default } + + $lngStr = Get-LanguageString "PolicyType.$($categoryObj.PolicyTypeLanguageId)" + + if($lngStr) { return $lngStr } + + return $defult } \ No newline at end of file diff --git a/Extensions/DocumentationHTML.psm1 b/Extensions/DocumentationHTML.psm1 index 08f979e..fb4affd 100644 --- a/Extensions/DocumentationHTML.psm1 +++ b/Extensions/DocumentationHTML.psm1 @@ -1,6 +1,6 @@ function Get-ModuleVersion { - '1.0.0' + '1.0.1' } function Invoke-InitializeModule @@ -374,7 +374,7 @@ function Invoke-HTMLProcessItem $isFilterAssignment = $false foreach($assignment in $documentedObj.Assignments) { - if(($assignment.target.PSObject.Properties | Where Name -eq "deviceAndAppManagementAssignmentFilterType")) + if(($assignment.PSObject.Properties | Where Name -eq "FilterMode")) { $isFilterAssignment = $true break diff --git a/Extensions/DocumentationMD.psm1 b/Extensions/DocumentationMD.psm1 index 8946f68..e78d485 100644 --- a/Extensions/DocumentationMD.psm1 +++ b/Extensions/DocumentationMD.psm1 @@ -1,6 +1,6 @@ function Get-ModuleVersion { - '1.1.0' + '1.1.1' } function Invoke-InitializeModule @@ -331,7 +331,7 @@ function Invoke-MDProcessItem $isFilterAssignment = $false foreach($assignment in $documentedObj.Assignments) { - if(($assignment.target.PSObject.Properties | Where Name -eq "deviceAndAppManagementAssignmentFilterType")) + if(($assignment.PSObject.Properties | Where Name -eq "FilterMode")) { $isFilterAssignment = $true break diff --git a/Extensions/DocumentationWord.psm1 b/Extensions/DocumentationWord.psm1 index cf4f2b2..047b45d 100644 --- a/Extensions/DocumentationWord.psm1 +++ b/Extensions/DocumentationWord.psm1 @@ -3,7 +3,7 @@ #https://docs.microsoft.com/en-us/office/vba/api/overview/word function Get-ModuleVersion { - '1.5.0' + '1.6.0' } function Invoke-InitializeModule @@ -600,7 +600,7 @@ function Invoke-WordProcessItem $isFilterAssignment = $false foreach($assignment in $documentedObj.Assignments) { - if(($assignment.target.PSObject.Properties | Where Name -eq "deviceAndAppManagementAssignmentFilterType")) + if(($assignment.PSObject.Properties | Where Name -eq "FilterMode")) { $isFilterAssignment = $true break @@ -752,7 +752,7 @@ function Add-DocTableItems $range = $script:doc.application.selection.range - $script:docTable = $script:doc.Tables.Add($range, ($items.Count + 1), $properties.Count, [Microsoft.Office.Interop.Word.WdDefaultTableBehavior]::wdWord9TableBehavior, [Microsoft.Office.Interop.Word.WdAutoFitBehavior]::wdAutoFitWindow) + $script:docTable = $script:doc.Tables.Add($range, (($items | measure).Count + 1), $properties.Count, [Microsoft.Office.Interop.Word.WdDefaultTableBehavior]::wdWord9TableBehavior, [Microsoft.Office.Interop.Word.WdAutoFitBehavior]::wdAutoFitWindow) $script:docTable.ApplyStyleHeadingRows = $true Set-DocObjectStyle $script:docTable $global:txtWordTableStyle.Text | Out-null diff --git a/Extensions/EndpointManager.psm1 b/Extensions/EndpointManager.psm1 index 3843620..7ef4aad 100644 --- a/Extensions/EndpointManager.psm1 +++ b/Extensions/EndpointManager.psm1 @@ -10,7 +10,7 @@ This module is for the Endpoint Manager/Intune View. It manages Export/Import/Co #> function Get-ModuleVersion { - '3.9.2' + '3.9.3' } function Invoke-InitializeModule @@ -703,6 +703,7 @@ function Invoke-InitializeModule ExpandAssignmentsList = $false PreFilesImportCommand = { Start-PreFilesImportADMXFiles @args } PreImportCommand = { Start-PreImportADMXFiles @args } + PostImportCommand = { Start-PostImportADMXFiles @args } PreDeleteCommand = { Start-PreDeleteADMXFiles @args } ViewProperties = @("fileName","status","Id") PropertiesToRemove = @("languageCodes","targetPrefix","targetNamespace","policyType","revision","status","uploadDateTime") @@ -848,6 +849,8 @@ function Invoke-EMSaveSettings function Invoke-GraphAuthenticationUpdated { Set-EMUIStatus + + $script:CustomADMXDefinitions = $null } function Set-EMUIStatus @@ -2023,10 +2026,9 @@ function local:Start-ImportApp if((Get-SettingValue "EMSaveEncryptionFile") -eq $true) { - #$fileEncryptionInfo = $fileEncryptionInfo | where { $null -ne $_.fileEncryptionInfo } if($fileEncryptionInfo) { - $jsonEncryptionInfo = $fileEncryptionInfo.fileEncryptionInfo | ConvertTo-Json -Depth 10 + $jsonEncryptionInfo = $fileEncryptionInfo | ConvertTo-Json -Depth 10 $pkgPath = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages") if($pkgPath -and [IO.Directory]::Exists($pkgPath)) @@ -2144,11 +2146,11 @@ function Add-DetailExtensionApplications $dlgSave.FileName = ($obj.FileName + ".encrypted") if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename) { - Start-DownloadAppContent $obj $dlgSave.FileName + $contentFileObj = Start-DownloadAppContent $obj $dlgSave.FileName if([IO.File]::Exists($dlgSave.FileName)) { - $fullPath = $pkgPath + "\$($obj.displayName)_$($obj.id)_$($obj.committedContentVersion).json" + $fullPath = Find-AppEncryptionFile $obj $contentFileObj $pkgPath if([IO.File]::Exists($fullPath) -eq $false) { if(([System.Windows.MessageBox]::Show("Could not find decryption file for $($obj.displayName)`nApp Id: $($obj.id)`nContent version $($obj.committedContentVersion)`n`nDo you want to browse for the file?", "Encryption file not found", "YesNo", "Warning")) -eq "Yes") @@ -2170,8 +2172,16 @@ function Add-DetailExtensionApplications { Write-Status "Decrypting file" $encryptionInfo = ConvertFrom-Json (Get-Content -Path $fullPath -Raw) + if($encryptionInfo.fileEncryptionInfo) + { + $encryptionInfo = $encryptionInfo.fileEncryptionInfo + } $destination = $pkgPath + "\$($obj.FileName)" Start-DecryptFile $dlgSave.Filename $destination $encryptionInfo.encryptionKey $encryptionInfo.initializationVector + try { [IO.File]::Delete($dlgSave.Filename) } + catch { + Write-LogError "Failed to delete exported encrypted file" $_.Exception + } } else { @@ -2188,7 +2198,28 @@ function Add-DetailExtensionApplications { $tmp.Children.Insert($index, $btnDownload) } +} +function Find-AppEncryptionFile +{ + param($obj, $contentFileObj, $rootFolders) + + $search = @() + $search += "$($obj.displayName)_$($obj.id)_$($obj.committedContentVersion)" + $search += "$([IO.Path]::GetFileNameWithoutExtension($obj.fileName))_$($contentFileObj.size)" + $search += "$($obj.displayName)_$($contentFileObj.size)" + + foreach($rootFolder in $rootFolders) + { + foreach($searchName in $search) + { + $fullName = ($rootFolder + "\$($searchName).json") + if([IO.File]::Exists($fullName)) + { + return $fullName + } + } + } } function Start-PreImportAssignmentsApplications @@ -2278,7 +2309,7 @@ function Start-PostExportApplications Save-Setting "Intune" "ExportAppFile" $global:chkExportApplicationFile.IsChecked if($global:chkExportApplicationFile.IsChecked) { - $encryptioSource = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages") + $encryptionSource = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages") $pkgPath = $path if($pkgPath) @@ -2286,27 +2317,32 @@ function Start-PostExportApplications Write-Status "Download file" $exportFile = $pkgPath + "\$($obj.FileName).encrypted" - $encryptionFile = $encryptioSource + "\$($obj.displayName)_$($obj.id)_$($obj.committedContentVersion).json" + $contentFileObj = Start-DownloadAppContent $obj $exportFile -GetContentFileInfoOnly + $encryptionFile = Find-AppEncryptionFile $obj $contentFileObj $encryptionSource if($encryptionFile -and [IO.File]::Exists($encryptionFile)) { - Start-DownloadAppContent $obj $exportFile + Start-DownloadFile $contentFileObj.azureStorageUri $exportFile if([IO.File]::Exists($exportFile)) { Write-Status "Decrypting file" $encryptionInfo = ConvertFrom-Json (Get-Content -Path $encryptionFile -Raw) + if($encryptionInfo.fileEncryptionInfo) + { + $encryptionInfo = $encryptionInfo.fileEncryptionInfo + } $destination = $pkgPath + "\$($obj.FileName)" Start-DecryptFile $exportFile $destination $encryptionInfo.encryptionKey $encryptionInfo.initializationVector } try { [IO.File]::Delete($exportFile) } catch { - Write-LogError "Filed to delete exported encrypted file" $_.Exception + Write-LogError "Failed to delete exported encrypted file" $_.Exception } } else { - Write-Log "Cound not file encryption file `"$($obj.displayName)_$($obj.id)_$($obj.committedContentVersion).json`"" + Write-Log "Cound not file encryption file" } } } @@ -2537,7 +2573,7 @@ function Get-GPOObjectSettings "definition@odata.bind" = "$($global:graphURL)/deviceManagement/groupPolicyDefinitions('$($definitionValue.definition.id)')" } - if($GPOObj.policyConfigurationIngestionType -eq "Custom") + if($definitionValue.definition.categoryPath) { $obj.Add("#Definition_Id", $definitionValue.definition.id) $obj.Add("#Definition_displayName", $definitionValue.definition.displayName) @@ -2555,7 +2591,7 @@ function Get-GPOObjectSettings # Add presentation@odata.bind property that links the value to the presentation object $presentationValue | Add-Member -MemberType NoteProperty -Name "presentation@odata.bind" -Value "$($global:graphURL)/deviceManagement/groupPolicyDefinitions('$($definitionValue.definition.id)')/presentations('$($presentationValue.presentation.id)')" - if($GPOObj.policyConfigurationIngestionType -eq "Custom") + if($definitionValue.definition.categoryPath) { $presentationValue | Add-Member -MemberType NoteProperty -Name "#Presentation_Id" -Value $presentationValue.presentation.id $presentationValue | Add-Member -MemberType NoteProperty -Name "#Presentation_Label" -Value $presentationValue.presentation.label @@ -2579,15 +2615,15 @@ function Get-GPOObjectSettings function Import-GPOSetting { - param($obj, $settings, [switch]$CustomADMX) + param($obj, $settings) if($obj) { Write-Status "Import settings for $($obj.displayName)" - $isCustomADMX = $CustomADMX -eq $true + $hasCustomADMX = $null -ne ($settings | Where { $null -ne $_.'#Definition_categoryPath' }) - if($isCustomADMX) + if($hasCustomADMX) { Write-Status "Import custom ADMX settings" if(-not $script:CustomADMXDefinitions) @@ -2606,7 +2642,12 @@ function Import-GPOSetting Category = $tmpCat Presentations = $null } - $script:CustomADMXDefinitions.Add($key, $val) + try { + $script:CustomADMXDefinitions.Add($key, $val) + } + catch { + Write-Log "Failed to add '$($tmpDef.displayName)' in category '$($tmpDef.categoryPath)' of class $($tmpDef.classType)" 3 + } } } } @@ -2615,7 +2656,7 @@ function Import-GPOSetting foreach($setting in $settings) { - if($isCustomADMX -and $script:CustomADMXDefinitions -is [HashTable] -and $script:CustomADMXDefinitions.Count -gt 0) + if($setting.'#Definition_categoryPath' -and $script:CustomADMXDefinitions -is [HashTable] -and $script:CustomADMXDefinitions.Count -gt 0) { $defVal = $null $key = ($setting.'#Definition_displayName' + $setting.'#Definition_categoryPath' + $setting.'#Definition_classType').ToLower() @@ -2670,7 +2711,7 @@ function Import-GPOSetting Write-Log "Settings might not be available if imported in another environment" 3 } } - elseif($isCustomADMX) + elseif($setting.'#Definition_categoryPath') { Write-Log "Custom AMDX settings cannot be imported without ADMX file imported. Definitions not found" 2 continue @@ -2678,7 +2719,7 @@ function Import-GPOSetting Start-GraphPreImport $setting - if($true) #$isCustomADMX) + if($true) { foreach($tmpProp in (($setting.PSObject.Properties | Where Name -like "#*").Name)) { @@ -2743,7 +2784,7 @@ function Start-PostFileImportAdministrativeTemplate { $tmpObj = Get-GraphObjectFromFile $file - Import-GPOSetting $obj $settings -CustomADMX:($tmpObj.policyConfigurationIngestionType -eq "Custom") + Import-GPOSetting $obj $settings } } @@ -3468,6 +3509,8 @@ function Add-EMAssignmentsToExportFile { param($obj, $objectType, $path, $Url = "") + if($global:chkExportAssignments.IsChecked -ne $true) { return } + $fileName = (Get-GraphObjectName $obj $objectType) if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id) { @@ -3811,6 +3854,13 @@ function Start-PreImportADMXFiles $obj.defaultLanguageCode = "" } +function Start-PostImportADMXFiles +{ + param($obj, $objectType, $file) + + $script:CustomADMXDefinitions = $null +} + function Start-PreDeleteADMXFiles { param($obj, $objectType) diff --git a/Extensions/IntuneAppManagement.psm1 b/Extensions/IntuneAppManagement.psm1 index 0d31d8b..7f33087 100644 --- a/Extensions/IntuneAppManagement.psm1 +++ b/Extensions/IntuneAppManagement.psm1 @@ -10,7 +10,7 @@ This module manages Application objects in Intune e.g. uploading application fil #> function Get-ModuleVersion { - '3.9.2' + '3.9.3' } ######################################################################################### @@ -718,7 +718,7 @@ function Start-DecryptFile function Start-DownloadAppContent { - param($obj, $destinationFile) + 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)) @@ -756,5 +756,17 @@ function Start-DownloadAppContent } } } - Start-DownloadFile $contentFile.azureStorageUri $destinationFile + + 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 + } } \ No newline at end of file diff --git a/Extensions/IntuneFilterUsage.psm1 b/Extensions/IntuneFilterUsage.psm1 new file mode 100644 index 0000000..5336d27 --- /dev/null +++ b/Extensions/IntuneFilterUsage.psm1 @@ -0,0 +1,329 @@ +<# +.SYNOPSIS +Module for listing Intune assignment filter usage + +.DESCRIPTION + +.NOTES + Author: Mikael Karlsson +#> +function Get-ModuleVersion +{ + '1.0.0' +} + +function Invoke-InitializeModule +{ + Add-EMToolsViewItem (New-Object PSObject -Property @{ + Title = "Intune Filter Usage" + Id = "IntuneFilterUsage" + ViewID = "EMTools" + Permissons=@("DeviceManagementConfiguration.ReadWrite.All") + Icon="DeviceConfiguration" + ShowViewItem = { Show-IntuneToolsFilterUsage } + }) +} + +function Show-IntuneToolsFilterUsage +{ + if(-not $script:frmIntuneFilterUsage) + { + $script:frmIntuneFilterUsage = Get-XamlObject ($global:AppRootFolder + "\Xaml\IntuneToolsFiterUsage.xaml") #-AddVariables + + if(-not $script:frmIntuneFilterUsage) { return } + + Add-XamlEvent $script:frmIntuneFilterUsage "btnGetIntuneFilterUsage" "add_click" ({ + Write-Status "Get Intune Filter Usage" + Get-EMIntuneFilterUsage + Write-Status "" + }) + + Add-XamlEvent $script:frmIntuneFilterUsage "btnIntuneFilterUsageCopy" "add_click" ({ + $dgValues = Get-DataGridValues ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage")) + $dgValues | ConvertTo-Csv -NoTypeInformation | Set-Clipboard + }) + + Add-XamlEvent $script:frmIntuneFilterUsage "btnIntuneFilterUsagesSave" "add_click" ({ + + $dlgSave = New-Object -Typename System.Windows.Forms.SaveFileDialog + $dlgSave.FileName = $obj.FileName + $dlgSave.DefaultExt = "*.csv" + $dlgSave.Filter = "CSV (*.csv)|*.csv|All files (*.*)| *.*" + if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename) + { + $dgValues = Get-DataGridValues ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage")) + $dgValues | ConvertTo-Csv -NoTypeInformation | Out-File -LiteralPath $dlgSave.Filename -Encoding UTF8 -Force + } + }) + } + + $global:grdToolsMain.Children.Clear() + $global:grdToolsMain.Children.Add($frmIntuneFilterUsage) +} + +function Get-DataGridValues_old +{ + param($dataGrid) + + $dgColumns = $dataGrid.Columns + #$dgColumns = Get-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "Columns" + + $properties = @() + + foreach($tmpCol in $dgColumns) + { + $propName = $tmpCol.Binding.Path.Path + $properties += @{n=$tmpCol.Header;e=([Scriptblock]::Create("`$_.$propName"))} + } + + ($script:objFilterUsage | Select -Property $properties) +} + +function Get-EMIntuneFilterUsage +{ + param($rootDir) + + Write-Status "Gather Intune Filter Information" + + Set-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "ItemsSource" $null + + $objectType = Get-GraphObjectType "AssignmentFilters" + + $loadedGroups = @{} + $loadedGroups.Add("adadadad-808e-44e2-905a-0b7873a8a531","All Devices") + $loadedGroups.Add("acacacac-9df4-4c7d-9d50-4ef0226f57a9","All Users") + + $script:objFilters = (Invoke-GraphRequest -Url $objectType.API).Value + + $script:objFilterUsage = @() + $groupIDs = @() + + foreach($filter in $script:objFilters) + { + Write-Status "Get payloads for filter $($filter.displayName)" + + $payloads = (Invoke-GraphRequest -Url "$($objectType.API)/$($filter.ID)/payloads").value + + $batchObjs = @() + foreach($payload in $payloads) + { + $guid = (New-Guid).Guid + + $payloadsObj = @{ + Payload = $payload + ID = $guid + Requests = @() + } + + if($groupIDs -notcontains $payload.groupId) + { + $groupIDs += $payload.groupId + } + + $batchObjs += $payloadsObj + + if($payload.payloadType -eq "win32app") + { + $payloadsObj.Requests += [ordered]@{ + id = "$($guid)_deviceHealthScripts" + method = "GET" + url = "/deviceManagement/deviceHealthScripts/$($payload.payloadId)/?`$select=displayName,isGlobalScript" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"} + } + } + elseif($payload.payloadType -eq "application") + { + $payloadsObj.Requests += [ordered]@{ + id = "$($guid)_mobileApps" + method = "GET" + url = "//deviceAppManagement/mobileApps/$($payload.payloadId)/?`$select=displayName" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"} + } + } + else + { + $payloadsObj.Requests += [ordered]@{ + id = "$($guid)_deviceCompliancePolicies" + method = "GET" + url = "/deviceManagement/deviceCompliancePolicies/$($payload.payloadId)/?`$select=displayName" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"} + } + + $payloadsObj.Requests += [ordered]@{ + id = "$($guid)_deviceConfigurations" + method = "GET" + url = "/deviceManagement/deviceConfigurations/$($payload.payloadId)/?`$select=displayName" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"} + } + + $payloadsObj.Requests += [ordered]@{ + id = "$($guid)_mobileAppConfigurations" + method = "GET" + url = "/deviceAppManagement/mobileAppConfigurations/$($payload.payloadId)/?`$select=displayName" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"} + } + } + } + + if($batchObjs.Count -gt 0) + { + $objName = Get-GraphObjectName $filter $objectType + $responses = Invoke-GraphBatchRequest $batchObjs.Requests $objName -SkipWarnings + <# + $batchObj = [ordered]@{ + requests = @($batchObjs.Requests) + } + + $responses = (Invoke-GraphRequest -Url "`$batch" -Body ($batchObj | ConvertTo-Json -Depth 50 -Compress) -Method "POST").responses + #> + foreach($response in ($responses | Where Status -eq 200)) + { + $payload = ($batchObjs | Where { $response.id -like "$($_.ID)*"}).Payload + + if($payload.assignmentFilterType -eq "Include") + { + $filterType = "Include" + } + else + { + $filterType = "Exclude" + } + + $typeStr = $null + if($payload.payloadType -eq "application") + { + $typeStr = Get-LanguageString "AppType.windowsClassicApp" + } + elseif($payload.payloadType -eq "win32app") + { + $typeStr = "Proactive Remediations" + } + else + { + $typeStr = (Get-PolicyTypeName $response.body.'@odata.type' $payload.payloadType) + } + + if(-not $typeStr) { $typeStr = $payload.payloadType} + + $script:objFilterUsage += [PSCustomObject]@{ + FiterObject = $filter + PayloadObject = $payload + FilterName = $filter.displayName + PolicyName = $response.body.displayName + Type = $response.body.'@odata.type' + PayloadType = $typeStr + Mode = $filterType + GroupID = $payload.groupId + GroupName = $payload.groupId + } + } + } + } + + if($groupIDs.Count -gt 0) + { + $guid = (New-Guid).Guid + $groupObjs = @() + $x = 1 + foreach($groupID in $groupIDs) + { + if($loadedGroups.ContainsKey($groupID)) { continue } + $groupObjs += [ordered]@{ + id= "$($guid)_$x" + method="GET" + url="/groups/$($groupID)/?`$select=displayName,id" + headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadGroupAssignments_BatchItem"} + } + $x++ + } + + if($groupObjs.Count -gt 0) + { + $responses = Invoke-GraphBatchRequest $groupObjs "Groups" + <# + $batchObj = [ordered]@{ + requests = @($groupObjs) + } + + $responses = (Invoke-GraphRequest -Url "`$batch" -Body ($batchObj | ConvertTo-Json -Depth 50 -Compress) -Method "POST").responses + #> + foreach($response in ($responses | Where Status -eq 200)) + { + if($response.body.displayName -and $response.body.id -and $loadedGroups.ContainsKey($response.body.id) -eq $false) + { + $loadedGroups.Add($response.body.id, $response.body.displayName) + } + } + } + + foreach($groupID in $loadedGroups.Keys) + { + $filterObj = $script:objFilterUsage | WHere GroupID -eq $groupID + if($filterObj -and $loadedGroups[$groupID]) + { + $filterObj.GroupName = $loadedGroups[$groupID] + } + } + } + + Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_LostFocus" ({ + Invoke-IntueFilterUsageBoxChanged $this + }) + + Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_GotFocus" ({ + if($this.Tag -eq "1" -and $this.Text -eq "Filter") { $this.Text = "" } + Invoke-IntueFilterUsageBoxChanged $this ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage")) + }) + + Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_TextChanged" ({ + Invoke-IntueFilterUsageBoxChanged $this ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage")) + }) + + Invoke-IntueFilterUsageBoxChanged ($script:frmIntuneFilterUsage.FindName("txtIntuneFilterUsageFilter")) ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage")) + + $ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:objFilterUsage)) + + Set-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "ItemsSource" ([System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList)) +} + +function Invoke-IntueFilterUsageBoxChanged +{ + param($txtBox, $dgObject) + + $filter = $null + + if($txtBox.Text.Trim() -eq "" -and $txtBox.IsFocused -eq $false) + { + $txtBox.FontStyle = "Italic" + $txtBox.Tag = 1 + $txtBox.Text = "Filter" + $txtBox.Foreground="Lightgray" + } + elseif($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false) + { + + } + else + { + $txtBox.FontStyle = "Normal" + $txtBox.Tag = $null + $txtBox.Foreground="Black" + $txtBox.Background="White" + + if($txtBox.Text) + { + $filter = { + param ($item) + + return ($item.FilterName -match [regex]::Escape($txtBox.Text) -or $item.PolicyName -match [regex]::Escape($txtBox.Text) -or $item.GroupName -match [regex]::Escape($txtBox.Text) ) + } + } + } + + if($dgObject.ItemsSource -is [System.Windows.Data.ListCollectionView] -and $txtBox.IsFocused -eq $true) + { + # This causes odd behaviour with focus e.g. and item has to be clicked twice to be selected + $dgObject.ItemsSource.Filter = $filter + #$dgObject.ItemsSource.Refresh() + } +} diff --git a/Extensions/MSALAuthentication.psm1 b/Extensions/MSALAuthentication.psm1 index d48a956..c6c2c6f 100644 --- a/Extensions/MSALAuthentication.psm1 +++ b/Extensions/MSALAuthentication.psm1 @@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res #> function Get-ModuleVersion { - '3.9.2' + '3.9.3' } $global:msalAuthenticator = $null @@ -770,7 +770,7 @@ function Get-MSALApp [void] $appBuilder.WithClientName("CloudAPIPowerShellManagement") [void] $appBuilder.WithClientVersion($PSVersionTable.PSVersion) - Add-MSALProxy $appBuilder + Add-MSALProxy $appBuilder # Ceck if correct version... #$appBuilder.WithMultiCloudSupport($true) @@ -1148,7 +1148,7 @@ function Connect-MSALUser ######################################################################################################### try { - Write-Log "Get tennant list" + Write-Log "Get tenant list" # Can we reuse the app used for login? $appBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($global:appObj.ClientID) diff --git a/Extensions/MSGraph.psm1 b/Extensions/MSGraph.psm1 index 8b6a273..00e5966 100644 --- a/Extensions/MSGraph.psm1 +++ b/Extensions/MSGraph.psm1 @@ -10,7 +10,7 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob #> function Get-ModuleVersion { - '3.9.2' + '3.9.3' } $global:MSGraphGlobalApps = @( @@ -278,6 +278,7 @@ function Invoke-GraphAuthenticationUpdated $global:MigrationTableCacheId = $null $global:LoadedDependencyObjects = $null $global:migFileObj = $null + $global:AADObjectCache = $null } function Invoke-SettingsUpdated @@ -2719,7 +2720,7 @@ function Add-GroupMigrationObject } } else - { + { Write-Log "No group found with ID $($groupId). It might be deleted." 2 } } @@ -2738,7 +2739,7 @@ function Add-GraphMigrationObject # Check if object is already processed $graphObj = Get-GraphMigrationObject $objId - if(-not $graphObj) + if(-not $graphObj -and ($global:AADObjectCache.ContainsKey($objId) -eq $false)) { # Get object info $graphObj = Invoke-GraphRequest "$($grapAPI)/$objId" -ODataMetadata "none" -NoError @@ -2764,7 +2765,8 @@ function Add-GraphMigrationObject } else { - Write-Log "No $objTypeName found with ID $($groupId). It might be deleted." 2 + if($global:AADObjectCache.ContainsKey($objId) -eq $false) { $global:AADObjectCache.Add($objId, $null) } + Write-Log "No $objTypeName found with ID $($objId). It might be deleted." 2 } } @@ -3198,7 +3200,7 @@ function Export-GraphObject [IO.Directory]::CreateDirectory($exportFolder) | Out-Null } - if($chkExportAssignments.IsChecked -ne $true -and $obj.Assignments) + if($global:chkExportAssignments.IsChecked -ne $true -and $obj.Assignments) { Remove-Property $obj "Assignments" } @@ -3436,10 +3438,9 @@ function Get-GraphBatchObjects { param($objects, $txtNameFilter) - $curBatch = 1 $batchResults = @() $batchArr = @() - $batchTotal = 0 + $skipped = 0 $objectType = $null foreach($obj in $objects) @@ -3449,7 +3450,7 @@ function Get-GraphBatchObjects if($objName -and $txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter)) { - $batchTotal++ + $skipped++ } else { @@ -3460,31 +3461,74 @@ function Get-GraphBatchObjects url = (Get-GraphObject $obj.Object $obj.ObjectType -GetAPI) headers = @{"Accept"="application/json;odata.metadata=$ometadata"} } - } + } + } + + if($batchArr.Count -eq 0) { return } - if($batchArr.Count -eq 20 -or ($batchTotal + $batchArr.Count -eq $objects.Count)) + $batchResults = (Invoke-GraphBatchRequest $batchArr $objectType.Title).body + + if($batchResults.Count -ne ($objects.Count - $skipped)) + { + Write-Log "Not all batch objects returned. Expected $($objects.Count - $skipped) but only got $($batchResults.Count)" + } + + if($objectType -and $batchResults.Count -gt 0) + { + $batchResultsTmp = $batchResults + $batchResults = Add-GraphObjectProperties $batchResultsTmp $objectType -property $objectType.ViewProperties + + $curObj = 1 + foreach($obj in $batchResults) + { + if($obj.Object -and $obj.ObjectType.PostGetCommand) + { + Write-Status "Run PostGetCommand - $((Get-GraphObjectName $obj.Object $obj.ObjectType)) ($($curObj)/$(@($batchResults).Count))" -Force + & $obj.ObjectType.PostGetCommand $obj $obj.ObjectType + } + $curObj++ + } + } + $batchResults +} + +function Invoke-GraphBatchRequest +{ + param($batchObjects, $batchType, [switch]$SkipWarnings, [switch]$IncludedFailed) + + $batchArr = @() + $batchResults = @() + $batchTotal = 0 + $curBatch = 1 + + foreach($obj in $batchObjects) + { + $batchArr += $obj + + if($batchArr.Count -eq 20 -or (($batchTotal + $batchArr.Count) -eq $batchObjects.Count)) { $batchObj = [PSCustomObject]@{ - requests = $batchArr - } + requests = @($batchArr) + } + + Write-Status "Get batch $curBatch $batchType" -Force - Write-Status "Get batch $curBatch $($obj.ObjectType.Title)" -Force $batchTotal += $batchArr.Count $json = $batchObj | ConvertTo-Json -Depth 50 - $maxRetryCount = 10 $curRetry = 0 do - { + { $retry = $false $retryArr = @() $retryAfter = 0 - $tmpResults = Invoke-GraphRequest -Url "`$batch" -Content $json -HttpMethod "POST" -Batch #-Url $api -property $obj.ObjectType.ViewProperties -objectType $obj.ObjectType - + $tmpResults = Invoke-GraphRequest -Url "`$batch" -Body $json -Method "POST" + foreach($batchResult in ($tmpResults.responses | Sort -Property Id)) { - if($batchResult.Status -ne "200" -or -not $batchResult.body) - { + if($batchResult.Status -ge 300 -or -not $batchResult.body) + { $reqObj = $batchObj.requests | where id -eq $batchResult.Id if($batchResult.Status -eq 429 -and $reqObj) { @@ -3500,11 +3544,19 @@ function Get-GraphBatchObjects } else { - Write-Log "Batch result $($batchResult.Status) for URL $($reqObj.URL). Skipping..." 2 + if($SkipWarnings -ne $true) + { + Write-Log "Batch result $($batchResult.Status) for URL $($reqObj.URL). Skipping..." 2 + } + + if($IncludedFailed -eq $true) + { + $batchResults += $batchResult + } } continue } - $batchResults += $batchResult.body + $batchResults += $batchResult } if($retryArr.Count -gt 0) @@ -3521,7 +3573,7 @@ function Get-GraphBatchObjects $retry = $true $tmpBatchObj = [PSCustomObject]@{ requests = $retryArr - } + } $json = $tmpBatchObj | ConvertTo-Json -Depth 50 Start-Sleep -Seconds $retryAfter } @@ -3533,27 +3585,11 @@ function Get-GraphBatchObjects } } - if($batchResults.Count -ne $objects.Count) + if($batchResults.Count -ne $batchObjects.Count -and $SkipWarnings -ne $true) { - Write-Log "Not all batch objects returned. Expected $($objects.Count) but only got $($batchResults.Count)" + Write-Log "Not all batch objects returned. Expected $($batchObjects.Count) but only got $($batchResults.Count)" 2 } - if($objectType -and $batchResults.Count -gt 0) - { - $batchResultsTmp = $batchResults - $batchResults = Add-GraphObjectProperties $batchResultsTmp $objectType -property $objectType.ViewProperties - - $curObj = 1 - foreach($obj in $batchResults) - { - if($obj.Object -and $obj.ObjectType.PostGetCommand) - { - Write-Status "Get full info - $((Get-GraphObjectName $obj.Object $obj.ObjectType)) ($($curObj)/$(@($batchResults).Count))" -Force - & $obj.ObjectType.PostGetCommand $obj $obj.ObjectType - } - $curObj++ - } - } $batchResults } diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 8e00e8f..4f0ae9c 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,56 @@ # Release Notes +## 3.9.2 - 2023-12-11 + +**New features** + +- **New tool - Get Assignment Filter usage**
+ - List all policies and assignments with a Filter defined
+ Based on [Issue 141](https://github.com/Micke-K/IntuneManagement/issues/141)
+ **NOTE:** Start the tool from: Views -> Intune Tools -> Intune Filter Usage
+ +- **Batch Export of App Content Encryption Key from Intunewin files**
+ This script can export encryption keys from existing intunewin files
+ Example:
+ Export-EncrytionKeys -RootFolder C:\Intune\Packages -ExportFolder C:\Intune\Download
+ This will export the encryption key information for each .intunewinfiles under C:\Intune\Packages
+ One json file will be created (for each .intunwinfile) in the C:\Intune\Download folder
+ File name will be **<*IntunewinFileBaseName*>_<*UnencryptedFileSize*>.json**
+ Do **NOT** rename this file since the script will search for that file when downloading or exporting App content
+ The script will not require authentication and it will have no knowledge of apps in Intune
+ Filename and unencrypted file size is used as the identifier to match app content in Intune with encryption file
+ **Important notes:**
+ Exported and decrypted .intunewin files are not supported to use for import at the moment.
+ These files are just the "zip" version of the source and can be unzipped with any zip extraction tool
+ The .intunewin file used for import has the "zip" version of the file and an xml with the encryption information +
+ additional file information eg. msi properties, file size etc.
+ Use the exported unencrypted "zip" version to restore the original files. Re-run the packaging tool if it should be re-used as applications content
+
+ Please report any issues or create a discussion if there are any questions
+ Script is located: **<*RootFolder*>\Scripts\Export-EncrytionKeys.ps1**
+ +
+ +**Fixes** +- **Export**
+ - Fixed issue where Assignments were included in export even if 'Export Assignments' was unchecked
+ Based on [Issue 171](https://github.com/Micke-K/IntuneManagement/issues/171)
+ +- **Documentation**
+ - Fixed issue where filter was not documented on some policies
+ - Fixed issue with Word Output provider if a policy only had one settings
+ +- **Custom ADMX Files**
+ - Fixed bug with migrating custom policies between environments. Cache was not cleared when swapping tenants or imported additional ADMX files
+ - Fixed documentention issue with Administrative template policies in GCC environment. Name and Category was missing
+ Based on [Issue 174](https://github.com/Micke-K/IntuneManagement/issues/174)
+ - Custom ADMX based policies was missing properties when swapping tenant
+ Based on [Issue 124](https://github.com/Micke-K/IntuneManagement/issues/124)
+ +- **Generic**
+ - Fixed logging issues when processing objects with a group that was deleted. ID was not reported
+ - Generic Batch request function created to support other batch requests eg Groups
+
+ ## 3.9.2 - 2023-10-17 **New features** diff --git a/Scripts/Export-ExcryptionKeys.ps1 b/Scripts/Export-ExcryptionKeys.ps1 new file mode 100644 index 0000000..d80a1af --- /dev/null +++ b/Scripts/Export-ExcryptionKeys.ps1 @@ -0,0 +1,159 @@ +<# + Export encryption keys from .intunewin files. + This can be used when downloading intunewin files from Intune. + + This is a prt of the IntuneManage GitHub Repository + https://github.com/Micke-K/IntuneManagement/ + (c) Mikael Karlsson MIT License - https://github.com/Micke-K/IntuneManagement/blob/master/LICENSE + + Exprot file name will be _.json + Do NOT rename the exported file. The script will try to find excryption file based on the generated name. + + Encryption information is file specific. If the same .intunewin file is imported in multiple tenants, + the same ecryption file can be used to decrypt it when downloading or exporting the app content. + + .Sample + Export-EncrytionKeys -RootFolder C:\Intune\Packages -ExportFolder C:\Intune\Download + This will search C:\Intune\Packages and all subfolder for .intunewin files and export + the encryption keys to the C:\Intune\Download. +#> +param( + [Alias("RF")] + # Root folder where intunewin files are located. + $RootFolder, + [Alias("EF")] + # Folder where encryption files should be exported to + # If this is empty, the encryption file will be saved to the same folder as the intunewin file + $ExportFolder) + +function Export-IntunewinFileObject +{ + param($file, $objectName, $toFile) + + try + { + Add-Type -Assembly System.IO.Compression.FileSystem + + $zip = [IO.Compression.ZipFile]::OpenRead($file) + + $zip.Entries | where { $_.Name -like $objectName } | foreach { + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $toFile, $true) + } + + $zip.Dispose() + return $true + } + catch + { + Write-Warning "Failed to get info from $file. Error: $($_.Exception.Message)" + return $false + } + +} + +function Export-EncryptionKeys +{ + param( + [Parameter(ValueFromPipeline=$true)] + $fileInfo, + $exportFolder = $fileInfo.DirectoryName + ) + + begin + { + } + + process + { + if($fileInfo -isnot [IO.FileInfo]) { return } + + if(-not $exportFolder) { $exportFolder = $fileInfo.DirectoryName } + + $tmpFile = [IO.Path]::GetTempFileName() + + if((Export-IntunewinFileObject $fileInfo.FullName "detection.xml" $tmpFile) -ne $true) + { + return + } + + $tmpFI = [IO.FileInfo]$tmpFile + + try + { + if($tmpFI.Length -eq 0) + { + throw "Detection.xml not exported" + } + [xml]$DetectionXML = Get-Content $tmpFile + } + catch + { + Write-Warning "Failed to export detection.xml file. Error: $($_.Exception.Message)" + return + } + finally + { + Remove-Item -Path $tmpFile -Force | Out-Null + } + + # 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 + + $fileData = @{} + $fileData.Name = $DetectionXML.ApplicationInfo.Name + $fileData.UnencryptedContentSize = $DetectionXML.ApplicationInfo.UnencryptedContentSize + $fileData.SetupFile = $DetectionXML.ApplicationInfo.SetupFile + + $msiInfo = @{} + if($DetectionXML.ApplicationInfo.MsiInfo) + { + $msiInfo.MsiPublisher = $DetectionXML.ApplicationInfo.MsiInfo.MsiPublisher + $msiInfo.MsiProductCode = $DetectionXML.ApplicationInfo.MsiInfo.Publisher + $msiInfo.MsiProductVersion = $DetectionXML.ApplicationInfo.MsiInfo.MsiProductVersion + $msiInfo.MsiPackageCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiPackageCode + $msiInfo.MsiUpgradeCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiUpgradeCode + $msiInfo.MsiIsMachineInstall = $DetectionXML.ApplicationInfo.MsiInfo.MsiIsMachineInstall + $msiInfo.MsiIsUserInstall = $DetectionXML.ApplicationInfo.MsiInfo.MsiIsUserInstall + $msiInfo.MsiIncludesServices = $DetectionXML.ApplicationInfo.MsiInfo.MsiIncludesServices + $msiInfo.MsiIncludesODBCDataSource = $DetectionXML.ApplicationInfo.MsiInfo.MsiIncludesODBCDataSource + $msiInfo.MsiContainsSystemRegistryKeys = $DetectionXML.ApplicationInfo.MsiInfo.MsiContainsSystemRegistryKeys + $msiInfo.MsiContainsSystemFolders = $DetectionXML.ApplicationInfo.MsiInfo.MsiContainsSystemFolders + } + # Create mobileAppContentFile object for the file + $fileEncryptionInfo = @{} + $fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo + $fileEncryptionInfo.fileData = $fileData + if($msiInfo.Count -gt 0) + { + $fileEncryptionInfo.MsiInfo = $msiInfo + } + + $json = $fileEncryptionInfo | ConvertTo-Json -Depth 10 + + if([IO.Directory]::Exists($exportFolder) -eq $false) + { + md $exportFolder | Out-Null + } + + $fileName = $exportFolder + "\$($fileInfo.BaseName)_$($DetectionXML.ApplicationInfo.UnencryptedContentSize).json" + + Write-Host "Save encryption for $($fileInfo.BaseName) file $fileName" + $json | Out-File -FilePath $fileName -Force -Encoding utf8 + } + + end + { + } + +} + +Get-ChildItem -Path $RootFolder -Filter "*.intunewin" -Recurse | Export-EncryptionKeys -exportFolder $ExportFolder \ No newline at end of file diff --git a/Xaml/EndpointManagerToolsIntuneAssignments.xaml b/Xaml/EndpointManagerToolsIntuneAssignments.xaml index 058b8ef..76f2cf4 100644 --- a/Xaml/EndpointManagerToolsIntuneAssignments.xaml +++ b/Xaml/EndpointManagerToolsIntuneAssignments.xaml @@ -41,22 +41,13 @@