This commit is contained in:
Mikael Karlsson
2023-12-11 18:58:13 +11:00
parent ab7b062946
commit f5613442bd
15 changed files with 824 additions and 93 deletions

View File

@@ -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 = @()

View File

@@ -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 *

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -1,4 +1,56 @@
# Release Notes
## 3.9.2 - 2023-12-11
**New features**
- **New tool - Get Assignment Filter usage**<br />
- List all policies and assignments with a Filter defined<br />
Based on [Issue 141](https://github.com/Micke-K/IntuneManagement/issues/141)<br />
**NOTE:** Start the tool from: Views -> Intune Tools -> Intune Filter Usage<br />
- **Batch Export of App Content Encryption Key from Intunewin files**<br />
This script can export encryption keys from existing intunewin files<br />
Example:<br />
Export-EncrytionKeys -RootFolder C:\Intune\Packages -ExportFolder C:\Intune\Download<br />
This will export the encryption key information for each .intunewinfiles under C:\Intune\Packages<br />
One json file will be created (for each .intunwinfile) in the C:\Intune\Download folder<br />
File name will be **<*IntunewinFileBaseName*>_<*UnencryptedFileSize*>.json**<br />
Do **NOT** rename this file since the script will search for that file when downloading or exporting App content<br />
The script will not require authentication and it will have no knowledge of apps in Intune<br />
Filename and unencrypted file size is used as the identifier to match app content in Intune with encryption file<br />
**Important notes:**<br />
Exported and decrypted .intunewin files are not supported to use for import at the moment.<br />
These files are just the "zip" version of the source and can be unzipped with any zip extraction tool<br />
The .intunewin file used for import has the "zip" version of the file and an xml with the encryption information +<br />
additional file information eg. msi properties, file size etc.<br />
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<br />
<br />
Please report any issues or create a discussion if there are any questions<br />
Script is located: **<*RootFolder*>\Scripts\Export-EncrytionKeys.ps1**<br />
<br />
**Fixes**
- **Export**<br />
- Fixed issue where Assignments were included in export even if 'Export Assignments' was unchecked<br />
Based on [Issue 171](https://github.com/Micke-K/IntuneManagement/issues/171)<br />
- **Documentation**<br />
- Fixed issue where filter was not documented on some policies<br />
- Fixed issue with Word Output provider if a policy only had one settings<br />
- **Custom ADMX Files**<br />
- Fixed bug with migrating custom policies between environments. Cache was not cleared when swapping tenants or imported additional ADMX files<br />
- Fixed documentention issue with Administrative template policies in GCC environment. Name and Category was missing<br />
Based on [Issue 174](https://github.com/Micke-K/IntuneManagement/issues/174)<br />
- Custom ADMX based policies was missing properties when swapping tenant<br />
Based on [Issue 124](https://github.com/Micke-K/IntuneManagement/issues/124)<br />
- **Generic**<br />
- Fixed logging issues when processing objects with a group that was deleted. ID was not reported<br />
- Generic Batch request function created to support other batch requests eg Groups<br />
<br />
## 3.9.2 - 2023-10-17
**New features**

View File

@@ -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 <IntunewinFileBaseName>_<UnencryptedFileSize>.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

View File

@@ -41,22 +41,13 @@
<Button Grid.Column="4" Name="btnGetIntuneAssignments" Padding="5,2,5,2" Content="Get Assignments" ToolTip="Get assignments from the selected exported folder" />
</Grid>
<StackPanel Grid.Row='1' Orientation="Horizontal" Margin="0,0,5,5" >
<StackPanel Grid.Row='1' Orientation="Horizontal" Margin="0,0,5,0" >
<Label Content="Filter" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Filter rows" />
</StackPanel>
<Grid Grid.Column='1' Grid.Row='1'>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Text="" Name="txtIntuneAssignmentsFilter" ToolTip="Filter items" />
</Grid>
<TextBox Text="" Grid.Column='1' Grid.Row='1' Margin="0,2,0,2" Name="txtIntuneAssignmentsFilter" ToolTip="Filter items" />
</Grid>
<DataGrid Name="dgIntuneAssignments" Margin="0,5,0,0" Grid.Row="1"

View File

@@ -0,0 +1,54 @@
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Grid.IsSharedSizeScope='True'>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Name="btnGetIntuneFilterUsage" Grid.Column='1' Grid.Row='0' Width="150" Padding="5,2,5,2" Content="Get Filter Usage" ToolTip="Get all Intune Filter assignment usage" />
<StackPanel Grid.Row='2' Orientation="Horizontal" Margin="5,0,0,4" >
<Label Content="Filter" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Filter rows" />
</StackPanel>
<TextBox Grid.Column='1' Grid.Row='2' Text="" Margin="5,3,0,5" Name="txtIntuneFilterUsageFilter" ToolTip="Filter items" />
</Grid>
<DataGrid Name="dgIntuneFilterUsage" Margin="0,5,0,0" Grid.Row="1"
AutoGenerateColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow"
CanUserAddRows="False"
ItemsSource="">
<DataGrid.Columns>
<DataGridTextColumn Header="Filter Name" Binding="{Binding FilterName}" IsReadOnly="True" />
<DataGridTextColumn Header="Policy Name" Binding="{Binding PolicyName}" IsReadOnly="True" />
<DataGridTextColumn Header="Type" Binding="{Binding PayloadType, Mode=OneWay}" IsReadOnly="True" />
<DataGridTextColumn Header="Mode" Binding="{Binding Mode}" IsReadOnly="True" />
<DataGridTextColumn Header="Group" Binding="{Binding GroupName}" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,0" >
<Button Name="btnIntuneFilterUsageCopy" Content="Copy" MinWidth="100" Margin="0,0,5,0" ToolTip="Copy the Filter usage as a CSV to the clipboard" />
<Button Name="btnIntuneFilterUsagesSave" Content="Save" MinWidth="100" />
</StackPanel>
</Grid>