Compare commits

..
5 Commits
Author SHA1 Message Date
Mikael Karlsson 47968b4219 3.9.5 2024-01-24 21:25:53 +11:00
Mikael Karlsson 6ac211a2bf 3.9.4 2023-12-18 19:37:17 +11:00
Mikael Karlsson 83c845fc33 3.9.3 Fix 2023-12-11 19:00:23 +11:00
Mikael Karlsson f5613442bd 3.9.3 2023-12-11 18:58:13 +11:00
Mikael Karlsson ab7b062946 3.9.2 2023-10-17 20:34:44 +11:00
43 changed files with 12168 additions and 7249 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ public class HttpFactoryWithProxy : IMsalHttpClientFactory
{
private static HttpClient _httpClient;
public public HttpFactoryWithProxy(string proxyURI) : this(proxyURI, null, null)
public HttpFactoryWithProxy(string proxyURI) : this(proxyURI, null, null)
{
}
+2 -2
View File
@@ -12,7 +12,7 @@
RootModule = 'CloudAPIPowerShellManagement.psm1'
# Version number of this module.
ModuleVersion = '3.9.1'
ModuleVersion = '3.9.5'
# 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 = @()
+12 -4
View File
@@ -93,9 +93,18 @@ function Initialize-CloudAPIManagement
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Add-Type -AssemblyName PresentationFramework
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$global:hideUI = ($Silent -eq $true)
$global:SilentBatchFile = $SilentBatchFile
if($tenantId)
{
$global:AzureAppId = $appId
$global:ClientSecret = $secret
$global:ClientCert = $certificate
}
if($global:hideUI -ne $true)
{
# Run with UI
@@ -126,12 +135,11 @@ function Initialize-CloudAPIManagement
Write-Error "Tenant Id is missing. Use -TenantId <Tenant-guid> on the command line to run silent batch jobs"
return
}
$global:TenantId = $tenantId
$global:AzureAppId = $appId
$global:ClientSecret = $secret
$global:ClientCert = $certificate
}
$global:TenantId = $tenantId
if($ShowConsoleWindow -ne $true)
{
Hide-Console
+91 -1
View File
@@ -11,7 +11,7 @@ This module handles the WPF UI
function Get-ModuleVersion
{
'3.9.1'
'3.9.5'
}
function Initialize-Window
@@ -2770,6 +2770,96 @@ function Get-ProxyURI
return $script:proxyURI
}
function Start-DownloadFile
{
param($sourceURL, $targetFile)
Write-Log "Download file from $sourceURL"
if(-not $sourceURL)
{
return
}
if(-not $targetFile)
{
Write-Log "Target file is missing"
return
}
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
$wc = New-Object System.Net.WebClient
$wc.Encoding = [System.Text.Encoding]::UTF8
$proxyURI = Get-ProxyURI
if($proxyURI)
{
$wc.Proxy = [System.Net.WebProxy]::new($proxyURI)
}
try
{
$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"
}
catch
{
Write-LogError "Failed to download file" $_.Exception
}
finally
{
$wc.Dispose()
}
}
function Get-ASCIIBytes
{
param($String)
$bytes = [System.Text.Encoding]::ASCII.GetBytes($String)
if ($bytes[0] -eq 0x2b -and $bytes[1] -eq 0x2f -and $bytes[2] -eq 0x76)
{ [Text.Encoding]::UTF7.GetBytes($String) }
elseif ($bytes[0] -eq 0xff -and $bytes[1] -eq 0xfe)
{ [Text.Encoding]::Unicode.GetBytes($String) }
elseif ($bytes[0] -eq 0xfe -and $bytes[1] -eq 0xff)
{ [Text.Encoding]::BigEndianUnicode.GetBytes($String) }
elseif ($bytes[0] -eq 0x00 -and $bytes[1] -eq 0x00 -and $bytes[2] -eq 0xfe -and $bytes[3] -eq 0xff)
{ [Text.Encoding]::UTF32.GetBytes($String) }
elseif ($bytes[0] -eq 0xef -and $bytes[1] -eq 0xbb -and $bytes[2] -eq 0xbf)
{ [Text.Encoding]::UTF8.GetBytes($String) }
$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 *
+2 -2
View File
@@ -1099,12 +1099,12 @@
"category": "SettingDetails.dependencyCategory"
},
{
"nameResourceKey": "supersedenceCategory",
"nameResourceKey": "AppRelationshipStatus.Tabs.supersedence",
"descriptionResourceKey": "",
"entityKey": "supersededApps",
"dataType": 20,
"booleanActions": 0,
"category": "SettingDetails.supersedenceCategory"
"category": "AppRelationshipStatus.Tabs.supersedence"
}
]
}
@@ -393,7 +393,7 @@
"value": "notConfigured"
},
{
"nameResourceKey": "microsoftEdge",
"nameResourceKey": "microsoftEdgeOptionText",
"descriptionResourceKey": "",
"value": "microsoftEdge"
},
@@ -543,7 +543,7 @@
{
"nameResourceKey": "TAPSettings.Tap.numberOfDays",
"descriptionResourceKey": "",
"entityKey": "pinRequiredInsteadOfBiometricTimeout",
"entityKey": "periodBeforePinReset",
"dataType": 100,
"booleanActions": 0,
"category": "SettingDetails.accessRequirements"
@@ -306,7 +306,7 @@
{
"nameResourceKey": "TAPSettings.EncryptData.label",
"descriptionResourceKey": "TAPSettings.EncryptData.tooltip",
"entityKey": "appDataEncryptionType",
"entityKey": "encryptOrgData",
"dataType": 0,
"booleanActions": 101,
"category": "SettingDetails.dataProtection",
@@ -354,7 +354,7 @@
"value": "notConfigured"
},
{
"nameResourceKey": "microsoftEdge",
"nameResourceKey": "microsoftEdgeOptionText",
"descriptionResourceKey": "",
"value": "microsoftEdge"
},
@@ -398,14 +398,12 @@
}
]
},
{
"dataType": 8,
"nameResourceKey": "empty"
},
{
"nameResourceKey": "TAPSettings.PinAccess.label",
"descriptionResourceKey": "TAPSettings.PinAccess.tooltip",
@@ -496,17 +494,17 @@
{
"nameResourceKey": "TAPSettings.Tap.numberOfDays",
"descriptionResourceKey": "",
"entityKey": "pinRequiredInsteadOfBiometricTimeout",
"entityKey": "periodBeforePinReset",
"dataType": 100,
"booleanActions": 0,
"category": "SettingDetails.accessRequirements"
},
{
"nameResourceKey": "TAPSettings.Tap.previousPinBlockCount",
"descriptionResourceKey": "",
"entityKey": "previousPinBlockCount",
"dataType": 14,
"booleanActions": 0,
"nameResourceKey": "TAPSettings.AppPIN.label",
"descriptionResourceKey": "TAPSettings.AppPIN.tooltip",
"entityKey": "disableAppPinIfDevicePinIsSet",
"dataType": 0,
"booleanActions": 201,
"category": "SettingDetails.accessRequirements"
},
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+41 -5
View File
@@ -20,7 +20,7 @@ $global:documentationProviders = @()
function Get-ModuleVersion
{
'2.0.2'
'2.1.0'
}
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
@@ -1053,6 +1056,11 @@ function Invoke-TranslateSettingsObject
#>
$cfgSettings = (Invoke-GraphRequest "/deviceManagement/configurationPolicies('$($obj.Id)')/settings?`$expand=settingDefinitions&top=1000" -ODataMetadata "minimal" @params).Value
if($obj.'@ObjectFromFile')
{
$cfgSettings = $obj.Settings
}
if(-not $global:cfgCategories)
{
$global:cfgCategories = (Invoke-GraphRequest "/deviceManagement/configurationCategories?`$filter=platforms has 'windows10' and technologies has 'mdm'" -ODataMetadata "minimal" @params).Value
@@ -1061,7 +1069,14 @@ function Invoke-TranslateSettingsObject
$script:settingCatalogasCategories = @{}
foreach($cfgSetting in $cfgSettings)
{
$defObj = $cfgSetting.settingDefinitions | Where id -eq $cfgSetting.settingInstance.settingDefinitionId
if($obj.'@ObjectFromFile' -and -not $cfgSetting.settingDefinitions)
{
$defObj = Invoke-GraphRequest "/deviceManagement/configurationSettings/$($cfgSetting.settingInstance.settingDefinitionId)"
}
else
{
$defObj = $cfgSetting.settingDefinitions | Where id -eq $cfgSetting.settingInstance.settingDefinitionId
}
#$defObj = $cfgSetting.settingDefinitions | Where { $_.id -eq $cfgSetting.settingInstance.settingDefinitionId -or $_.id -eq $cfgSettings.settingInstanceTemplate.settingDefinitionId }
if(-not $defObj -or $script:settingCatalogasCategories.ContainsKey($defObj.categoryId)) { continue }
@@ -1108,6 +1123,10 @@ function Add-SettingsSetting
$childSettings = @()
$settingsDef = $settingsDefs | Where id -eq $settingInstance.settingDefinitionId
if(-not $settingsDef -and $settingInstance.settingDefinitionId)
{
$settingsDef = Invoke-GraphRequest "/deviceManagement/configurationSettings/$($settingInstance.settingDefinitionId)"
}
$categoryDef = $global:cfgCategories | Where Id -eq $settingsDef.categoryId #$script:settingCatalogasCategories[$settingsDef.categoryId]
if($settingsDef.categoryId -ne $categoryDef.rootCategoryId)
@@ -1924,7 +1943,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
}
@@ -2088,6 +2108,7 @@ function Invoke-TranslateSection
if($prop.dataType -eq 8)
{
if($prop.nameResourceKey -eq "LearnMore") { continue }
elseif($prop.nameResourceKey -eq "Empty") { $script:CurrentSubCategory = $null }
elseif($prop.nameResourceKey -in $script:categoriesToIgnore) { continue }
elseif($prop.nameResourceKey)
{
@@ -4432,7 +4453,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)
{
@@ -5041,3 +5062,18 @@ 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
}
+355 -140
View File
@@ -10,7 +10,7 @@ This module will also document some objects based on PowerShell functions
function Get-ModuleVersion
{
'1.6.1'
'1.6.4'
}
function Invoke-InitializeModule
@@ -35,6 +35,7 @@ function Initialize-CDDocumentation
{
$script:allTenantApps = $null
$script:allTermsOfUse = $null
$script:allAuthenticationStrength = $null
$script:allAuthenticationContextClasses = $null
$script:allCustomCompliancePolicies = $null
}
@@ -760,9 +761,10 @@ function Add-CDDocumentCustomProfileProperty
}
elseif($obj.'@OData.Type' -eq "#microsoft.graph.androidManagedAppProtection")
{
$obj | Add-Member Noteproperty -Name "overrideFingerprint" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null)
$obj | Add-Member Noteproperty -Name "pinReset" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null)
$obj | Add-Member Noteproperty -Name "overrideFingerprint" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null -and $obj.pinRequiredInsteadOfBiometricTimeout -ne "PT0S")
$obj | Add-Member Noteproperty -Name "pinReset" -Value ($obj.periodBeforePinReset -ne $null -and $obj.periodBeforePinReset -ne "PT0S")
$obj | Add-Member Noteproperty -Name "managedBrowserSelection" -Value (?: $obj.customBrowserPackageId "unmanagedBrowser" $obj.managedBrowser)
$obj | Add-Member Noteproperty -Name "encryptOrgData" -Value ($obj.appDataEncryptionType -ne "useDeviceSettings")
$retValue = $true
}
@@ -785,9 +787,10 @@ function Add-CDDocumentCustomProfileProperty
$obj | Add-Member Noteproperty -Name "sendDataSelector" -Value $sendDataOption
$obj | Add-Member Noteproperty -Name "overrideFingerprint" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null)
$obj | Add-Member Noteproperty -Name "pinReset" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null)
$obj | Add-Member Noteproperty -Name "overrideFingerprint" -Value ($obj.pinRequiredInsteadOfBiometricTimeout -ne $null -and $obj.pinRequiredInsteadOfBiometricTimeout -ne "PT0S")
$obj | Add-Member Noteproperty -Name "pinReset" -Value ($obj.periodBeforePinReset -ne $null -and $obj.periodBeforePinReset -ne "PT0S")
$obj | Add-Member Noteproperty -Name "managedBrowserSelection" -Value (?: $obj.customBrowserPackageId "unmanagedBrowser" $obj.managedBrowser)
$obj | Add-Member Noteproperty -Name "encryptOrgData" -Value ($obj.appDataEncryptionType -ne "useDeviceSettings")
$retValue = $true
}
@@ -2241,96 +2244,272 @@ function Invoke-CDDocumentConditionalAccess
Add-BasicAdditionalValues $obj $objectType
###################################################
# User and groups
###################################################
$ids = @()
foreach($id in ($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups + $obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups))
{
if($id -in $ids) { continue }
elseif($id -eq "GuestsOrExternalUsers") { continue }
elseif($id -eq "All") { continue }
elseif($id -eq "None") { continue }
$ids += $id
}
$roleIds = @()
foreach($id in ($obj.conditions.users.includeRoles + $obj.conditions.users.excludeRoles))
{
if($id -in $ids) { continue }
$roleIds += $id
}
$idInfo = $null
if($ids.Count -gt 0)
{
$ht = @{}
$ht.Add("ids", @($ids | Unique))
$body = $ht | ConvertTo-Json
# ToDo: Get from MigFile for Offline
$idInfo = (Invoke-GraphRequest -Url "/directoryObjects/getByIds?`$select=displayName,id" -Content $body -Method "Post").Value
}
if($roleIds.Count -gt 0 -and -not $script:allAadRoles)
{
$script:allAadRoles =(Invoke-GraphRequest -url "/directoryRoleTemplates?`$select=Id,displayName" -ODataMetadata "minimal").value
}
$includeLabel = Get-LanguageString "AzureCA.userSelectionBladeIncludeTabTitle"
$excludeLabel = Get-LanguageString "AzureCA.userSelectionBladeExcludeTabTitle"
$category = Get-LanguageString "AzureCA.usersGroupsLabel"
if($obj.conditions.clientApplications.includeServicePrincipals -or $obj.conditions.clientApplications.excludeServicePrincipals)
{
###################################################
# Workload
###################################################
if((($obj.conditions.users.includeUsers | Where { $_ -eq "All"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.allUsersString"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
}
elseif((($obj.conditions.users.includeUsers | Where { $_ -eq "None"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.chooseApplicationsNone"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
}
else
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.userSelectionBladeSelectedUsers"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
$ids = @()
foreach($id in ($obj.conditions.clientApplications.includeServicePrincipals + $obj.conditions.clientApplications.excludeServicePrincipals))
{
if($id -in $ids) { continue }
elseif($id -eq "ServicePrincipalsInMyTenant") { continue }
if((($obj.conditions.users.includeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null))
$ids += $id
}
$category = Get-LanguageString "AzureCA.workloadIdentities"
$idInfo = $null
if($ids.Count -gt 0)
{
$ht = @{}
$ht.Add("ids", @($ids | Unique))
$body = $ht | ConvertTo-Json
# ToDo: Get from MigFile for Offline
$idInfo = (Invoke-GraphRequest -Url "/directoryObjects/getByIds?`$select=displayName,id" -Content $body -Method "Post").Value
}
if((($obj.conditions.clientApplications.includeServicePrincipals | Where { $_ -eq "ServicePrincipalsInMyTenant"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.allGuestUserLabel"
Value = Get-LanguageString "Inputs.enabled" #$((?: (($obj.conditions.users.includeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null) "enabled" "disabled"))"
Name = $includeLabel
Value = Get-LanguageString "AzureCA.servicePrincipalRadioAll"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeGuestsOrExternalUsers"
EntityKey = "includeServicePrincipals"
})
}
elseif((($obj.conditions.clientApplications.includeServicePrincipals | Where { $_ -eq "None"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.chooseApplicationsNone"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeServicePrincipals"
})
}
elseif($ids.Count -gt 0 -and $obj.conditions.clientApplications.includeServicePrincipals)
{
#$category = Get-LanguageString "AzureCA.selectedSP"
$tmpObjs = @()
foreach($id in ($obj.conditions.clientApplications.includeServicePrincipals))
{
$idObj = $idInfo | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
if($tmpObjs.count -gt 0)
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $category
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $includeLabel
EntityKey = "includeServicePrincipals"
})
}
}
if($obj.conditions.clientApplications.servicePrincipalFilter)
{
if($obj.conditions.clientApplications.servicePrincipalFilter.mode -eq "include")
{
$filterMode = "included"
}
else
{
$filterMode = "excluded"
}
#AzureCA.PolicyBlade.Conditions.DeviceAttributes.AssignmentFilter.Blade
#AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.title
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.AppliesTo.$filterMode"
Value = $obj.conditions.clientApplications.servicePrincipalFilter.rule
Category = $category
SubCategory = Get-LanguageString "AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.title"
EntityKey = "excludeServicePrincipalDevices"
})
}
if($obj.conditions.users.includeRoles.Count -gt 0)
if((($obj.conditions.clientApplications.excludeServicePrincipals | Where { $_ -eq "ServicePrincipalsInMyTenant"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.servicePrincipalRadioAll"
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeServicePrincipals"
})
}
elseif($ids.Count -gt 0)
{
#$category = Get-LanguageString "AzureCA.selectedSP"
$tmpObjs = @()
foreach($id in ($obj.conditions.clientApplications.excludeServicePrincipals))
{
$idObj = $idInfo | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
if($tmpObjs.count -gt 0)
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $category
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeServicePrincipals"
})
}
}
}
else
{
###################################################
# User and groups
###################################################
$ids = @()
foreach($id in ($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups + $obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups))
{
if($id -in $ids) { continue }
elseif($id -eq "GuestsOrExternalUsers") { continue }
elseif($id -eq "All") { continue }
elseif($id -eq "None") { continue }
$ids += $id
}
$roleIds = @()
foreach($id in ($obj.conditions.users.includeRoles + $obj.conditions.users.excludeRoles))
{
if($id -in $ids) { continue }
$roleIds += $id
}
$idInfo = $null
if($ids.Count -gt 0)
{
$ht = @{}
$ht.Add("ids", @($ids | Unique))
$body = $ht | ConvertTo-Json
# ToDo: Get from MigFile for Offline
$idInfo = (Invoke-GraphRequest -Url "/directoryObjects/getByIds?`$select=displayName,id" -Content $body -Method "Post").Value
}
if($roleIds.Count -gt 0 -and -not $script:allAadRoles)
{
$script:allAadRoles =(Invoke-GraphRequest -url "/directoryRoleTemplates?`$select=Id,displayName" -ODataMetadata "minimal").value
}
$category = Get-LanguageString "AzureCA.usersGroupsLabel"
if((($obj.conditions.users.includeUsers | Where { $_ -eq "All"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.allUsersString"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
}
elseif((($obj.conditions.users.includeUsers | Where { $_ -eq "None"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.chooseApplicationsNone"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
}
else
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = $includeLabel
Value = Get-LanguageString "AzureCA.userSelectionBladeSelectedUsers"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsers"
})
if((($obj.conditions.users.includeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.allGuestUserLabel"
Value = Get-LanguageString "Inputs.enabled" #$((?: (($obj.conditions.users.includeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null) "enabled" "disabled"))"
Category = $category
SubCategory = $includeLabel
EntityKey = "includeGuestsOrExternalUsers"
})
}
if($obj.conditions.users.includeRoles.Count -gt 0)
{
$tmpObjs = @()
foreach($id in $obj.conditions.users.includeRoles)
{
$idObj = $script:allAadRoles | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.directoryRolesLabel"
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $includeLabel
EntityKey = "includeRoles"
})
}
if(($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups).Count -gt 0)
{
$tmpObjs = @()
foreach($id in ($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups))
{
if($id -eq "GuestsOrExternalUsers") { continue }
$idObj = $idInfo | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = $category
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsersGroups"
})
}
}
if((($obj.conditions.users.excludeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.allGuestUserLabel"
Value = Get-LanguageString "Inputs.enabled" #$((?: (($obj.conditions.users.excludeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null) "enabled" "disabled"))"
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeGuestsOrExternalUsers"
})
}
if($obj.conditions.users.excludeRoles.Count -gt 0)
{
$tmpObjs = @()
foreach($id in $obj.conditions.users.includeRoles)
foreach($id in $obj.conditions.users.excludeRoles)
{
$idObj = $script:allAadRoles | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
@@ -2340,78 +2519,31 @@ function Invoke-CDDocumentConditionalAccess
Name = Get-LanguageString "AzureCA.directoryRolesLabel"
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $includeLabel
EntityKey = "includeRoles"
SubCategory = $excludeLabel
EntityKey = "excludeRoles"
})
}
if(($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups).Count -gt 0)
if(($obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups).Count -gt 0)
{
$tmpObjs = @()
foreach($id in ($obj.conditions.users.includeUsers + $obj.conditions.users.includeGroups))
foreach($id in ($obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups))
{
if($id -eq "GuestsOrExternalUsers") { continue }
$idObj = $idInfo | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = $category
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $includeLabel
EntityKey = "includeUsersGroups"
SubCategory = $excludeLabel
EntityKey = "excludeUsersGroups"
})
}
}
if((($obj.conditions.users.excludeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null))
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.allGuestUserLabel"
Value = Get-LanguageString "Inputs.enabled" #$((?: (($obj.conditions.users.excludeUsers | Where { $_ -eq "GuestsOrExternalUsers"}) -ne $null) "enabled" "disabled"))"
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeGuestsOrExternalUsers"
})
}
if($obj.conditions.users.excludeRoles.Count -gt 0)
{
$tmpObjs = @()
foreach($id in $obj.conditions.users.excludeRoles)
{
$idObj = $script:allAadRoles | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.directoryRolesLabel"
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeRoles"
})
}
if(($obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups).Count -gt 0)
{
$tmpObjs = @()
foreach($id in ($obj.conditions.users.excludeUsers + $obj.conditions.users.excludeGroups))
{
if($id -eq "GuestsOrExternalUsers") { continue }
$idObj = $idInfo | Where Id -eq $id
$tmpObjs += ?? $idObj.displayName $id
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = $category
Value = $tmpObjs -join $script:objectSeparator
Category = $category
SubCategory = $excludeLabel
EntityKey = "excludeUsersGroups"
})
}
###################################################
# Cloud apps or actions
###################################################
@@ -2630,6 +2762,19 @@ function Invoke-CDDocumentConditionalAccess
elseif($script:allTermsOfUse -isnot [Object[]]) { $script:allTermsOfUse = @($script:allTermsOfUse ) }
}
<#
if(-not $script:allAuthenticationStrength -and (($obj.grantControls.authenticationStrength | measure).Count -gt 0))
{
$script:allAuthenticationStrength = Get-DocOfflineObjects "AuthenticationStrengths"
if(-not $script:allAuthenticationStrength)
{
$script:allAuthenticationStrength = (Invoke-GraphRequest -url "/identity/conditionalAccess/authenticationStrengths/policies?`$select=displayName,Id" -ODataMetadata "minimal").value
}
if(-not $script:allAuthenticationStrength ) { $script:allAuthenticationStrength = @()}
elseif($script:allAuthenticationStrength -isnot [Object[]]) { $script:allAuthenticationStrength = @($script:allAuthenticationStrength ) }
}
#>
if($obj.conditions.locations.includeLocations.Count -gt 0)
{
$tmpObjs = @()
@@ -2745,6 +2890,28 @@ function Invoke-CDDocumentConditionalAccess
})
}
if($obj.conditions.devices.deviceFilter)
{
if($obj.conditions.devices.deviceFilter.mode -eq "include")
{
$filterMode = "included"
}
else
{
$filterMode = "excluded"
}
#AzureCA.PolicyBlade.Conditions.DeviceAttributes.AssignmentFilter.Blade
#AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.title
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.AppliesTo.$filterMode"
Value = $obj.conditions.devices.deviceFilter.rule
Category = $category
SubCategory = Get-LanguageString "AzureCA.PolicyBlade.Conditions.DeviceAttributes.Blade.title"
EntityKey = "includeDevices"
})
}
###################################################
# Grant
###################################################
@@ -2848,6 +3015,34 @@ function Invoke-CDDocumentConditionalAccess
})
}
if(($obj.grantControls.authenticationStrength | measure).Count -gt 0)
{
$authenticationStrngth = @()
foreach($tmpId in $obj.grantControls.authenticationStrength)
{
$authenticationStrngth += ?? $obj.grantControls.authenticationStrength.displayName $tmpId
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.WhatIfBlade.authenticationStrength"
Value = $termsOfUse -join $script:objectSeparator
Category = $category
SubCategory = ""
EntityKey = "authenticationStrength"
})
}
if(($obj.grantControls.customAuthenticationFactors | measure).Count -gt 0)
{
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.menuItemClaimProviderControls"
Value = $obj.grantControls.customAuthenticationFactors -join $script:objectSeparator
Category = $category
SubCategory = ""
EntityKey = "customAuthenticationFactors"
})
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.descriptionContentForControlsAndOr"
Value = Get-LanguageString "AzureCA.$((?: ($obj.grantControls.operator -eq "OR") "requireOneControlText" "requireAllControlsText"))"
@@ -2891,10 +3086,6 @@ function Invoke-CDDocumentConditionalAccess
if($obj.sessionControls.signInFrequency.isEnabled -eq $true)
{
if($obj.sessionControls.cloudAppSecurity.cloudAppSecurityType -eq "mcasConfigured") { $strId = "useCustomControls" }
elseif($obj.sessionControls.cloudAppSecurity.cloudAppSecurityType -eq "monitorOnly") { $strId = "monitorOnly" }
elseif($obj.sessionControls.cloudAppSecurity.cloudAppSecurityType -eq "blockDownloads") { $strId = "blockDownloads" }
if($obj.sessionControls.signInFrequency.type -eq "hours")
{
if($obj.sessionControls.signInFrequency.value -gt 1)
@@ -2906,7 +3097,7 @@ function Invoke-CDDocumentConditionalAccess
$value = Get-LanguageString "AzureCA.SessionLifetime.SignInFrequency.Option.Hour.singular"
}
}
else
elseif($obj.sessionControls.signInFrequency.type -eq "days")
{
if($obj.sessionControls.signInFrequency.value -gt 1)
{
@@ -2917,6 +3108,10 @@ function Invoke-CDDocumentConditionalAccess
$value = Get-LanguageString "AzureCA.SessionLifetime.SignInFrequency.Option.Day.singular"
}
}
else
{
$value = Get-LanguageString "AzureCA.SessionControls.SignInFrequency.everytime"
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.SessionLifetime.SignInFrequency.Option.label"
@@ -2927,6 +3122,26 @@ function Invoke-CDDocumentConditionalAccess
})
}
if($null -ne $obj.sessionControls.continuousAccessEvaluation)
{
if($obj.sessionControls.continuousAccessEvaluation.mode -eq "strictLocation")
{
$value = Get-LanguageString "AzureCA.SessionControls.Cae.strictLocation"
}
else
{
$value = Get-LanguageString "AzureCA.SessionControls.Cae.disable"
}
Add-CustomSettingObject ([PSCustomObject]@{
Name = Get-LanguageString "AzureCA.SessionControls.Cae.checkboxLabel"
Value = $value
Category = $category
SubCategory = ""
EntityKey = "continuousAccessEvaluation"
})
}
if($obj.sessionControls.persistentBrowser.isEnabled -eq $true)
{
Add-CustomSettingObject ([PSCustomObject]@{
+2 -2
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
+2 -2
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
+3 -3
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
+374 -37
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.1'
'3.9.5'
}
function Invoke-InitializeModule
@@ -73,6 +73,21 @@ function Invoke-InitializeModule
SubPath = "EndpointManager"
}) "EndpointManager"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Save Encryption File"
Key = "EMSaveEncryptionFile"
Type = "Boolean"
Description = "Save encryption file when uploading an app. This can then be used to when downloading the app file."
SubPath = "EndpointManager"
}) "EndpointManager"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "App download folder"
Key = "EMIntuneAppDownloadFolder"
Type = "Folder"
Description = "Folder where app packages will be downloaded and where encryption files will be saved"
SubPath = "EndpointManager"
}) "EndpointManager"
$viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerPanel.xaml") -AddVariables
@@ -314,7 +329,7 @@ function Invoke-InitializeModule
PostFileImportCommand = { Start-PostFileImportAdministrativeTemplate @args }
PreImportCommand = { Start-PreImportAdministrativeTemplate @args }
LoadObject = { Start-LoadAdministrativeTemplate @args }
PropertiesToRemove = @("definitionValues")
PropertiesToRemove = @("definitionValues","policyConfigurationIngestionType")
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
Icon="DeviceConfiguration"
GroupId = "DeviceConfiguration"
@@ -454,8 +469,10 @@ function Invoke-InitializeModule
PreDeleteCommand = { Start-PreDeleteApplications @args }
PostExportCommand = { Start-PostExportApplications @args }
PostListCommand = { Start-PostListApplications @args }
ExportExtension = { Add-ScriptExportExtensions @args }
ExportExtension = { Add-ScriptExportApplications @args }
PostGetCommand = { Start-PostGetApplications @args }
PostImportCommand = { Start-PostImportApplications @args }
PostFilesImportCommand = { Start-PostFilesImportApplications @args }
GroupId = "Apps"
ScopeTagsReturnedInList = $false
})
@@ -686,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")
@@ -831,6 +849,8 @@ function Invoke-EMSaveSettings
function Invoke-GraphAuthenticationUpdated
{
Set-EMUIStatus
$script:CustomADMXDefinitions = $null
}
function Set-EMUIStatus
@@ -1111,7 +1131,7 @@ function Start-PostExportEndpointSecurity
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
$fileName = (Get-GraphObjectName $obj $objectType).Trim('.')
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
@@ -1985,24 +2005,40 @@ function local:Start-ImportApp
if($appType -eq "microsoft.graph.win32LobApp")
{
Copy-Win32LOBPackage $packageFile $obj
$fileEncryptionInfo = Copy-Win32LOBPackage $packageFile $obj
}
elseif($appType -eq "microsoft.graph.windowsMobileMSI")
{
Copy-MSILOB $packageFile $obj
$fileEncryptionInfo = Copy-MSILOB $packageFile $obj
}
elseif($appType -eq "microsoft.graph.iosLOBApp")
{
Copy-iOSLOB $packageFile $obj
$fileEncryptionInfo = Copy-iOSLOB $packageFile $obj
}
elseif($appType -eq "microsoft.graph.androidLOBApp")
{
Copy-AndroidLOB $packageFile $obj
$fileEncryptionInfo = Copy-AndroidLOB $packageFile $obj
}
else
{
Write-Log "Unsupported application type $appType. File will not be uploaded" 2
}
if((Get-SettingValue "EMSaveEncryptionFile") -eq $true)
{
if($fileEncryptionInfo)
{
$jsonEncryptionInfo = $fileEncryptionInfo | ConvertTo-Json -Depth 10
$pkgPath = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages")
if($pkgPath -and [IO.Directory]::Exists($pkgPath))
{
$obj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($obj.id)" -ODataMetadata "Minimal"
$fullPath = $pkgPath + "\$($obj.displayName)_$($obj.id)_$($obj.committedContentVersion).json"
$jsonEncryptionInfo | Out-File -FilePath $fullPath -Force -Encoding utf8
}
}
}
}
function Start-PreUpdateApplication
@@ -2092,6 +2128,101 @@ function Add-DetailExtensionApplications
$tmp.Children.Insert($index, $btnUpload)
}
$btnDownload = New-Object System.Windows.Controls.Button
$btnDownload.Content = 'Download'
$btnDownload.Name = 'btnDownloadAppfile'
$btnDownload.Margin = "0,0,5,0"
$btnDownload.Width = "100"
$btnDownload.Add_Click({
Write-Status "Download file"
$obj = $global:dgObjects.SelectedItem.Object
#$obj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($obj.id)"
$pkgPath = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages")
$dlgSave = [System.Windows.Forms.SaveFileDialog]::new()
$dlgSave.InitialDirectory = $pkgPath
$dlgSave.FileName = ($obj.FileName + ".encrypted")
$dlgSave.DefaultExt = "*.encrypted"
$dlgSave.Filter = "Encrypted intunewin (*.encrypted)|*.encrypted|All files (*.*)|*.*"
if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename)
{
$contentFileObj = Start-DownloadAppContent $obj $dlgSave.FileName
if([IO.File]::Exists($dlgSave.FileName))
{
$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")
{
$of = [System.Windows.Forms.OpenFileDialog]::new()
$of.InitialDirectory = $pkgPath
$of.DefaultExt = "*.json"
$of.Filter = "Json (*.json)|*.json"
$of.Multiselect = $false
if($of.ShowDialog() -eq "OK")
{
$fullPath = $of.FileName
}
}
}
if([IO.File]::Exists($fullPath))
{
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
{
Write-Log "Decryption file for $($obj.displayName) not found. Skipping decryption" 2
}
}
}
Write-Status ""
})
$tmp = $form.FindName($buttonPanel)
if($tmp)
{
$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
@@ -2177,6 +2308,47 @@ function Start-PostExportApplications
Write-LogError "Failed to export scripts" $_.Exception
}
}
Save-Setting "Intune" "ExportAppFile" $global:chkExportApplicationFile.IsChecked
if($global:chkExportApplicationFile.IsChecked)
{
$encryptionSource = Get-SettingValue "EMIntuneAppDownloadFolder" (Get-SettingValue "EMIntuneAppPackages")
$pkgPath = $path
if($pkgPath)
{
Write-Status "Download file"
$exportFile = $pkgPath + "\$($obj.FileName).encrypted"
$contentFileObj = Start-DownloadAppContent $obj $exportFile -GetContentFileInfoOnly
$encryptionFile = Find-AppEncryptionFile $obj $contentFileObj $encryptionSource
if($encryptionFile -and [IO.File]::Exists($encryptionFile))
{
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 "Failed to delete exported encrypted file" $_.Exception
}
}
else
{
Write-Log "Cound not file encryption file"
}
}
}
}
function Start-PostListApplications
@@ -2204,26 +2376,174 @@ function Start-PostListApplications
$objList
}
function Add-ScriptExportApplications
{
param($form, $buttonPanel, $index = 0)
Add-ScriptExportExtensions $form $buttonPanel $index
$ctrl = $form.FindName("chkExportApplicationFile")
if(-not $ctrl)
{
$xaml = @"
<StackPanel $($global:wpfNS) Orientation="Horizontal" Margin="0,0,5,0">
<Label Content="Export application file" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Export the application file. Note: Application file will only be exported if ecryption file is found." />
</StackPanel>
"@
$label = [Windows.Markup.XamlReader]::Parse($xaml)
$global:chkExportApplicationFile = [System.Windows.Controls.CheckBox]::new()
$global:chkExportApplicationFile.IsChecked = ((Get-Setting "Intune" "ExportAppFile" "false") -eq "true")
$global:chkExportApplicationFile.VerticalAlignment = "Center"
$global:chkExportApplicationFile.Name = "chkExportApplicationFile"
@($label, $global:chkExportApplicationFile)
}
}
function Start-PostGetApplications {
param($obj, $objectType)
$relationships = (Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($obj.Id)/relationships?`$filter=targetType%20eq%20microsoft.graph.mobileAppRelationshipType%27child%27").value
$dependencyApps = @()
$supersededApps = @()
foreach ($rel in $relationships) {
if ($rel."@odata.type" -eq "#microsoft.graph.mobileAppDependency") {
$dependencyApps += "$($rel.targetDisplayName)|!|$($rel.targetDisplayVersion)|!|$($rel.targetId)|!|$($rel.dependencyType)"
if($obj.Object.dependentAppCount -is [Int] -and ($obj.Object.dependentAppCount -gt 0 -or $obj.Object.supersededAppCount -gt 0)) {
$relationships = (Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($obj.Id)/relationships?`$filter=targetType%20eq%20microsoft.graph.mobileAppRelationshipType%27child%27").value
$dependencyApps = @()
$supersededApps = @()
foreach ($rel in $relationships) {
if ($rel."@odata.type" -eq "#microsoft.graph.mobileAppDependency") {
$dependencyApps += "$($rel.targetDisplayName)|!|$($rel.targetDisplayVersion)|!|$($rel.targetId)|!|$($rel.dependencyType)"
}
elseif ($rel."@odata.type" -eq "#microsoft.graph.mobileAppSupersedence") {
$supersededApps += "$($rel.targetDisplayName)|!|$($rel.targetDisplayVersion)|!|$($rel.targetId)|!|$($rel.supersedenceType)"
}
}
elseif ($rel."@odata.type" -eq "#microsoft.graph.mobileAppSupersedence") {
$supersededApps += "$($rel.targetDisplayName)|!|$($rel.targetDisplayVersion)|!|$($rel.targetId)|!|$($rel.supersedenceType)"
if ($dependencyApps.Count -gt 0) {
$obj.Object | Add-Member -MemberType NoteProperty -Name "#CustomRefDependency" -Value ($dependencyApps -join "|*|")
}
}
if ($dependencyApps.Count -gt 0) {
$obj.Object | Add-Member -MemberType NoteProperty -Name "#CustomRefDependency" -Value ($dependencyApps -join "|*|")
}
if ($supersededApps.Count -gt 0) {
$obj.Object | Add-Member -MemberType NoteProperty -Name "#CustomRefSupersedence" -Value ($supersededApps -join "|*|")
if ($supersededApps.Count -gt 0) {
$obj.Object | Add-Member -MemberType NoteProperty -Name "#CustomRefSupersedence" -Value ($supersededApps -join "|*|")
}
}
}
function Start-PostImportApplications
{
param($obj, $objectType, $file)
#$tmpObj = Get-GraphObjectFromFile $file
}
function Start-PostFilesImportApplications
{
param($objType, $importedObjects, $importedFiles)
$refObjects = $importedFiles | Where { $null -ne $_.Object."#CustomRefDependency" -or $null -ne $_.Object."#CustomRefSupersedence" }
if(($refObjects | measure).Count -gt 0)
{
Write-Log "Applicetions with Depnedency or Supersedence detected"
foreach($file in $refObjects)
{
Add-ApplicationReferences $file.ImportedObject $file.Object
}
}
}
function local:Add-ApplicationReferences
{
param($obj, $fileObj)
if($fileObj."#CustomRefDependency" -or $fileObj."#CustomRefSupersedence")
{
Write-Log "Adding app references for $($obj.displayName)"
$depAppsInfo = $fileObj."#CustomRefDependency"
$supAppsInfo = $fileObj."#CustomRefSupersedence"
$releationShips = [PSCustomObject]@{
relationships = @()
}
if($depAppsInfo)
{
foreach($depApp in ($depAppsInfo -split "[|][*][|]"))
{
$appName, $appVer, $appId, $appType = $depApp -split "[|][!][|]"
if(-not $appName -or -not $appVer)
{
Write-Log "Could not get Name and Version from string: $appApp" 2
continue
}
$tmpApps = (Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps?`$filter=displayName eq '$appName'").value
if(-not $tmpApps)
{
Write-Log "No application found with name $appName" 2
continue
}
$tmpApp = $tmpApps | Where displayVersion -eq $appVer
if(-not $tmpApp)
{
Write-Log "No $appName application found with version $appVer" 2
continue
}
elseif(-not ($tmpApp | measure).Count -gt 1)
{
Write-Log "Multiple $appName application found with version $appVer" 2
continue
}
Write-Log "Add $appName ($appVer) to Dependency list"
$releationShips.relationships += [PSCustomObject]@{
"@odata.type" = "#microsoft.graph.mobileAppDependency"
targetId = $tmpApp.Id
dependencyType = $appType
}
}
}
if($supAppsInfo)
{
foreach($suppApp in ($supAppsInfo -split "[|][*][|]"))
{
$appName, $appVer, $appId, $appType = $suppApp -split "[|][!][|]"
if(-not $appName -or -not $appVer)
{
Write-Log "Could not get Name and Version from string: $appApp" 2
continue
}
$tmpApps = (Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps?`$filter=displayName eq '$appName'").value
if(-not $tmpApps)
{
Write-Log "No application found with name $appName" 2
continue
}
$tmpApp = $tmpApps | Where displayVersion -eq $appVer
if(-not $tmpApp)
{
Write-Log "No $appName application found with version $appVer" 2
continue
}
elseif(-not ($tmpApp | measure).Count -gt 1)
{
Write-Log "Multiple $appName application found with version $appVer" 2
continue
}
Write-Log "Add $appName ($appVer) to Supersedence list"
$releationShips.relationships += [PSCustomObject]@{
"@odata.type" = "#microsoft.graph.mobileAppSupersedence"
targetId = $tmpApp.Id
supersedenceType = $appType
}
}
}
if($releationShips.relationships.Count -gt 0)
{
$json = Update-JsonForEnvironment (ConvertTo-Json $releationShips -Depth 20)
Write-Log "Update app references"
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$($obj.Id)/updateRelationships" -Method "POST" -Body $json
}
}
}
@@ -2256,7 +2576,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)
@@ -2274,7 +2594,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
@@ -2298,15 +2618,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)
@@ -2325,7 +2645,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
}
}
}
}
@@ -2334,7 +2659,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()
@@ -2389,7 +2714,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
@@ -2397,7 +2722,7 @@ function Import-GPOSetting
Start-GraphPreImport $setting
if($true) #$isCustomADMX)
if($true)
{
foreach($tmpProp in (($setting.PSObject.Properties | Where Name -like "#*").Name))
{
@@ -2423,7 +2748,7 @@ function Start-PostExportAdministrativeTemplate
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
$fileName = (Get-GraphObjectName $obj $objectType).Trim('.')
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
@@ -2462,7 +2787,7 @@ function Start-PostFileImportAdministrativeTemplate
{
$tmpObj = Get-GraphObjectFromFile $file
Import-GPOSetting $obj $settings -CustomADMX:($tmpObj.policyConfigurationIngestionType -eq "Custom")
Import-GPOSetting $obj $settings
}
}
@@ -2687,7 +3012,7 @@ function Start-PostExportRoleDefinitions
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
$fileName = (Get-GraphObjectName $obj $objectType).Trim('.')
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
@@ -3187,7 +3512,9 @@ function Add-EMAssignmentsToExportFile
{
param($obj, $objectType, $path, $Url = "")
$fileName = (Get-GraphObjectName $obj $objectType)
if($global:chkExportAssignments.IsChecked -ne $true) { return }
$fileName = (Get-GraphObjectName $obj $objectType).Trim('.')
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
@@ -3515,10 +3842,13 @@ function Start-PreImportADMXFiles
return
}
$bytes = [IO.File]::ReadAllBytes($admxFile)
#$bytes = [IO.File]::ReadAllBytes($admxFile)
$bytes = Get-ASCIIBytes ([IO.File]::ReadAllText($admxFile))
$obj.content = [Convert]::ToBase64String($bytes)
$bytes = [IO.File]::ReadAllBytes($admlFile)
#$bytes = [IO.File]::ReadAllBytes($admlFile)
$bytes = Get-ASCIIBytes ([IO.File]::ReadAllText($admlFile))
$obj.groupPolicyUploadedLanguageFiles += [PSCustomObject]@{
fileName = [io.path]::GetFileName($admlFile)
content = [Convert]::ToBase64String($bytes)
@@ -3527,6 +3857,13 @@ function Start-PreImportADMXFiles
$obj.defaultLanguageCode = ""
}
function Start-PostImportADMXFiles
{
param($obj, $objectType, $file)
$script:CustomADMXDefinitions = $null
}
function Start-PreDeleteADMXFiles
{
param($obj, $objectType)
+147 -13
View File
@@ -10,7 +10,7 @@ This module manages Application objects in Intune e.g. uploading application fil
#>
function Get-ModuleVersion
{
'3.9.1'
'3.9.3'
}
#########################################################################################
@@ -94,14 +94,18 @@ function Copy-MSILOB
$tmpFile = [IO.Path]::GetTempFileName()
$msiInfo = Get-MSIFileInformation $msiFile @("ProductName", "ProductCode", "ProductVersion", "ProductLanguage")
$msiInfo = Get-MSIFileInformation $msiFile @("ProductName", "ProductCode", "ProductVersion", "ProductLanguage", "UpgradeCode", "ALLUSERS")
if(-not $msiInfo) { return }
$fileEncryptionInfo = New-IntuneEncryptedFile $msiFile $tmpFile
[xml]$manifestXML = '<MobileMsiData MsiExecutionContext="Any" MsiRequiresReboot="false" MsiUpgradeCode="" MsiIsMachineInstall="true" MsiIsUserInstall="false" MsiIncludesServices="false" MsiContainsSystemRegistryKeys="false" MsiContainsSystemFolders="false"></MobileMsiData>'
$manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["ProductCode"]
$manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["UpgradeCode"]
if($msiInfo["ALLUSERS"] -eq 1)
{
$manifestXML.MobileMsiData.MsiExecutionContext = "System"
}
$appFileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
@@ -109,11 +113,14 @@ function Copy-MSILOB
size = (Get-Item $msiFile).Length
sizeEncrypted = (Get-Item $tmpFile).Length
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
isDependency = $false
}
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-iOSLOB
@@ -149,6 +156,8 @@ function Copy-iOSLOB
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-AndroidLOB
@@ -185,6 +194,8 @@ function Copy-AndroidLOB
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
Remove-Item $tmpFile -Force
$fileEncryptionInfo
}
function Copy-Win32LOBPackage
@@ -235,7 +246,7 @@ function Copy-Win32LOBPackage
$fileBody = @{
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
name = $DetectionXML.ApplicationInfo.FileName
name = "IntunePackage.intunewin"
size = [int64]$DetectionXML.ApplicationInfo.UnencryptedContentSize
sizeEncrypted = (Get-Item $tmpIntunewinFile).Length
manifest = $null
@@ -246,45 +257,58 @@ function Copy-Win32LOBPackage
# Remove extracted inintunewin file
Remove-Item $tmpIntunewinPath -Force -Recurse
$fileEncryptionInfo
}
function Add-FileToIntuneApp
{
param($appId, $appType, $appFile, $fileBody)
$contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions" -HttpMethod POST -Content "{}"
$contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions" -HttpMethod POST -Content "{}" -ODataMetadata "Minimal"
$contentVersionId = $contentVersion.id
$fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5)
$fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5) -ODataMetadata "Minimal"
if(-not $fileObj)
{
return
}
Write-Log "File object created. ID: $($fileObj.id)"
# Wait for Azure storage URI
$fileObj = Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "AzureStorageUriRequest"
if(-not $fileObj)
{
Write-Log "No File Object returned from commit. Upload failed" 3
return
}
# Upload file
Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)"
Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" | Out-Null
# 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)
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)/commit" -HttpMethod POST -Content (ConvertTo-Json $fileEncryptionInfo -Depth 5) | Out-Null
Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile"
Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile" | Out-Null
$fiUpload = [IO.FileInfo]$appFile
# Commit the content version
$commitAppBody = @{
"@odata.type" = "#$appType"
committedContentVersion = $contentVersionId
fileName = $fiUpload.Name
}
$reponse = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5)
# if($fileBody.Name) {
# $fileUploadName = $fileBody.Name
# }
# else {
$fiUpload = [IO.FileInfo]$appFile
$fileUploadName = $fiUpload.Name
# }
$commitAppBody.Add("fileName",$fileUploadName)
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5) | Out-Null
Write-Log "Upload finished for file $fileUploadName version $contentVersionId"
}
function Wait-IntuneFileState
@@ -318,7 +342,7 @@ function Wait-IntuneFileState
return
}
Start-Sleep -s 5
Start-Sleep -Seconds 1
}
if($succes -eq $false)
@@ -636,3 +660,113 @@ function New-IntuneEncryptedFile
$fileEncryptionInfo
}
function Start-DecryptFile
{
param($sourceFile, $targetFile, $encryptionKey, $initializationVector)
if([IO.File]::Exists($targetFile))
{
$fi = [IO.FileInfo]$targetFile
$newName = $fi.Name + "_$((Get-Date).ToString("yyyyMMdd_HHmm"))" + $fi.Extension
$targetFile = $fi.DirectoryName + "\$newName"
Write-Log "Target file exists. Changing target file to $targetFile" 2
}
$bufferBlockSize = 1024 * 4
try
{
$aes = [System.Security.Cryptography.Aes]::Create()
$buffer = New-Object byte[] $bufferBlockSize
$bytesRead = 0
$targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try
{
$sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None)
$decryptor = $aes.CreateDecryptor([Convert]::FromBase64String($encryptionKey), [Convert]::FromBase64String($initializationVector))
$decryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
$sourceStream.Seek(48L, [System.IO.SeekOrigin]::Begin)
while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
{
$decryptoStream.Write($buffer, 0, $bytesRead)
$decryptoStream.Flush()
}
$decryptoStream.FlushFinalBlock()
}
finally
{
if ($null -ne $decryptoStream) { $decryptoStream.Dispose() }
if ($null -ne $targetStream) { $targetStream.Dispose() }
if ($null -ne $decryptor) { $decryptor.Dispose() }
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
}
}
finally
{
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
if ($null -ne $aes) { $aes.Dispose() }
}
}
function Start-DownloadAppContent
{
param($obj, $destinationFile, [switch]$GetContentFileInfoOnly)
# Not use but kept for reference. File can be download but it will be encrypted
if([IO.File]::Exists($destinationFile))
{
try { [IO.File]::Delete($encryptionFile) }
catch {}
}
$appId = $obj.Id
$appInfo = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId"
$appType = $appInfo.'@odata.type'.Trim('#')
#$contentVersions = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions"
#$contentVerId = $contentVersions.Value[0].id
$contentVerId = $appInfo.committedContentVersion
$contentFiles = Invoke-GraphRequest "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files"
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($contentFiles.value[-1].Id)" -NoError
if(-not $contentFile)
{
foreach($file in $contentFiles.value)
{
if($contentFiles.value[-1].Id -eq $file.id) { continune }
# NOT happy about this. file objects are not always returned in the order of upload.
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($file.Id)" -NoError
if($contentFile)
{
break
}
}
}
if($contentFile.azureStorageUri)
{
if($GetContentFileInfoOnly -ne $true)
{
Start-DownloadFile $contentFile.azureStorageUri $destinationFile
}
return $contentFile
}
else
{
Write-Log "Could not find file object for app $($obj.displayName) ($($appId))" 2
}
}
+422
View File
@@ -0,0 +1,422 @@
<#
.SYNOPSIS
Module for listing Intune assignment filter usage
.DESCRIPTION
.NOTES
Author: Mikael Karlsson
#>
function Get-ModuleVersion
{
'1.1.1'
}
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)"
$payloadsManual = @()
$payloads = (Invoke-GraphRequest -Url "$($objectType.API)/$($filter.ID)/payloads").value
$batchObjs = @()
foreach($payload in $payloads)
{
$guid = [Guid]::NewGuid().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"}
}
}
elseif($payload.payloadType -eq "deviceManagmentConfigurationAndCompliancePolicy")
{
$payloadsObj.Requests += [ordered]@{
id = "$($guid)_configurationPolicies"
method = "GET"
url = "/deviceManagement/configurationPolicies/$($payload.payloadId)/?`$select=name,platforms,technologies,templateReference"
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
}
}
elseif($payload.payloadType -eq "groupPolicyConfiguration")
{
$payloadsObj.Requests += [ordered]@{
id = "$($guid)_groupPolicyConfigurations"
method = "GET"
url = "/deviceManagement/groupPolicyConfigurations/$($payload.payloadId)/?`$select=displayName"
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
}
}
elseif($payload.payloadType -eq "enrollmentConfiguration")
{
if(-not $script:enrolmentConfigurations)
{
$script:enrolmentConfigurations = @()
$script:enrolmentConfigurations += (Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations?`$select=displayName,id,deviceEnrollmentConfigurationType").value
$script:enrolmentConfigurations += (Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations?`$select=displayName,id,deviceEnrollmentConfigurationType&`$filter=deviceEnrollmentConfigurationType eq 'EnrollmentNotificationsConfiguration'").value
}
$payloadsManual += $payload
<#
$payloadsObj.Requests += [ordered]@{
id = "$($guid)_enrollmentConfiguration"
method = "GET"
url = "/deviceManagement/deviceEnrollmentConfigurations/$($enrolmentConfig.Id)/?`$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
foreach($response in ($responses | Where Status -lt 300))
{
$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"
}
elseif($payload.payloadType -eq "groupPolicyConfiguration")
{
$typeStr = "Settings Catalog"
}
elseif($payload.payloadType -eq "deviceManagmentConfigurationAndCompliancePolicy")
{
$typeStr = "Administrative Templates"
}
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.Name $response.body.displayName
Type = $response.body.'@odata.type'
PayloadType = $typeStr
Mode = $filterType
GroupID = $payload.groupId
GroupName = $payload.groupId
}
}
foreach($response in ($responses | Where Status -ge 300))
{
$payload = ($batchObjs | Where { $response.id -like "$($_.ID)*"}).Payload
Write-Log "Failed to get info for payload with id $($payload.payloadId) of type $($payload.payloadType). Might be deleted or not supported." 2
}
}
foreach($payload in $payloadsManual)
{
$payloadPolicy = $script:enrolmentConfigurations | Where Id -like "$($payload.payloadId)*" | Select -First 1
if($payloadPolicy)
{
if($payloadPolicy.deviceEnrollmentConfigurationType -eq "enrollmentNotificationsConfiguration")
{
$typeStr = "Enrollment notifications"
}
elseif($payloadPolicy.deviceEnrollmentConfigurationType -eq "windows10EnrollmentCompletionPageConfiguration")
{
$typeStr = "Enrollment Status Page"
}
else
{
$typeStr = (Get-PolicyTypeName $payloadPolicy.body.'@odata.type' $payload.payloadType)
}
if($payload.assignmentFilterType -eq "Include")
{
$filterType = "Include"
}
else
{
$filterType = "Exclude"
}
$script:objFilterUsage += [PSCustomObject]@{
FiterObject = $filter
PayloadObject = $payload
FilterName = $filter.displayName
PolicyName = ?? $payloadPolicy.Name $payloadPolicy.displayName
Type = $payloadPolicy.'@odata.type'
PayloadType = $typeStr
Mode = $filterType
GroupID = $payload.groupId
GroupName = $payload.groupId
}
}
}
}
if($groupIDs.Count -gt 0)
{
$guid = [Guid]::NewGuid().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)
{
$filterObjs = $script:objFilterUsage | WHere GroupID -eq $groupID
if($filterObjs -and $loadedGroups[$groupID])
{
foreach($filterObj in $filterObjs) {
$filterObj.GroupName = $loadedGroups[$groupID]
}
}
}
$script:enrolmentConfigurations = $null
}
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()
}
}
+248 -131
View File
@@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res
#>
function Get-ModuleVersion
{
'3.9.1'
'3.9.3'
}
$global:msalAuthenticator = $null
@@ -158,6 +158,10 @@ function Clear-MSALCurentUserVaiables
{
$global:MSALTenantId = $null
$global:MSALGraphEnvironment = $null
$script:jwtAccessToken = $null
$script:jwtIdToken = $null
}
function Get-MSALCurrentApp
@@ -223,14 +227,23 @@ function Get-MSALUserInfo
if($global:MSALToken)
{
Write-Log "Get current user"
$tmpMe = MSGraph\Invoke-GraphRequest -Url "ME" -SkipAuthentication -ODataMetadata "Skip"
if($null -ne $tmpMe -and $tmpMe.creationType -ne "Invitation")
if($script:jwtAccessToken.Payload.idtyp -ne "app")
{
### Only get user info from home tenant
$global:Me = $tmpMe
Write-Log "Get profile picture"
$global:profilePhoto = "$($env:LOCALAPPDATA)\CloudAPIPowerShellManagement\$($global:Me.Id).jpeg"
MSGraph\Invoke-GraphRequest "me/photos/48x48/`$value" -OutFile $global:profilePhoto -SkipAuthentication -NoError | Out-Null
$tmpMe = MSGraph\Invoke-GraphRequest -Url "ME" -SkipAuthentication -ODataMetadata "Skip"
if($null -ne $tmpMe -and $tmpMe.creationType -ne "Invitation")
{
### Only get user info from home tenant
$global:Me = $tmpMe
Write-Log "Get profile picture"
$global:profilePhoto = "$($env:LOCALAPPDATA)\CloudAPIPowerShellManagement\$($global:Me.Id).jpeg"
MSGraph\Invoke-GraphRequest "me/photos/48x48/`$value" -OutFile $global:profilePhoto -SkipAuthentication -NoError | Out-Null
}
}
else
{
$global:profilePhoto = $null
$global:me = $script:jwtAccessToken.Payload.app_displayname
}
Write-Log "Get organization info"
@@ -834,6 +847,42 @@ function Connect-MSALUser
Write-LogDebug "Authenticate"
if($global:MainAppStarted -eq $false)
{
$script:AppLogin = (Get-SettingValue "GraphAzureAppLogin") -or ($global:TenantId -and $global:AzureAppId -and ($global:ClientSecret -or $global:ClientCert))
}
if($script:AppLogin)
{
if($global:MSALToken -and $global:MSALToken.ExpiresOn.LocalDateTime.Ticks -gt ((Get-Date).AddMinutes(-5)).Ticks)
{
return
}
# Get login info for silent job from settings
if(-not $global:AzureAppId) { $global:AzureAppId = Get-SettingValue "GraphAzureAppId" -TenantID $global:TenantId }
if(-not $global:ClientSecret -and -not $global:ClientCert) { $global:ClientSecret = Get-SettingValue "GraphAzureAppSecret" -TenantID $global:TenantId }
if(-not $global:ClientSecret -and -not $global:ClientCert) { $global:ClientCert = Get-SettingValue "GraphAzureAppCert" -TenantID $global:TenantId }
if($global:AzureAppId -and $global:ClientSecret -and $global:TenantId)
{
Connect-MSALClientApp $global:AzureAppId $global:TenantId -secret $global:ClientSecret
}
elseif($global:AzureAppId -and $global:ClientCert -and $global:TenantId)
{
Connect-MSALClientApp $global:AzureAppId $global:TenantId -certificate $global:ClientCert
}
else
{
Write-Log "Azure AppId, Tenant Id and Sercret/Cert must be specified for App logins" 3
}
Invoke-MSALAuthenticationUpdated $global:MSALToken
return
}
if($ShowMenu -eq $true -and ((Get-SettingValue "AzureADLoginMenu") -eq $true))
{
if((Show-MSALLoginMenu) -eq $false) { return }
@@ -1099,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)
@@ -1167,7 +1216,8 @@ function Connect-MSALUser
Save-Setting "" "LastLoggedOnUser" $authResult.Account.UserName
Save-Setting "" "LastLoggedOnUserId" $authResult.Account.HomeAccountId.ObjectId
}
Invoke-MSALAuthenticationUpdated $authResult
<#
Write-LogDebug "User, tenant or app has changed"
Get-MSALUserInfo
if($authResult)
@@ -1175,9 +1225,26 @@ function Connect-MSALUser
Invoke-MSALCheckObjectViewAccess $authResult
}
Invoke-ModuleFunction "Invoke-GraphAuthenticationUpdated"
#>
}
}
function local:Invoke-MSALAuthenticationUpdated
{
param($authResult)
Write-LogDebug "User, tenant or app has changed"
$script:jwtAccessToken = Get-JWTtoken $global:MSALToken.AccessToken
$script:jwtIdToken = Get-JWTtoken $global:MSALToken.IdToken
Get-MSALUserInfo
if($authResult)
{
Invoke-MSALCheckObjectViewAccess $authResult
}
Invoke-ModuleFunction "Invoke-GraphAuthenticationUpdated"
}
function Start-MSALConsentPrompt
{
param([switch]$PassThru, $authToken)
@@ -1254,6 +1321,22 @@ function Invoke-MSALCheckObjectViewAccess
Set-XamlProperty $script:userEllipsGrid "lnkRequestConsent" "Visibility" "Visible"
}
$accessToken = $null
if($authToken)
{
$accessToken = Get-JWTtoken $authToken.AccessToken
}
$curPermissions = $null
if($accessToken.Payload.idtyp -eq "app")
{
$curPermissions = $accessToken.Payload.roles
}
elseif($accessToken.Payload.scp)
{
$curPermissions = $accessToken.Payload.scp.Split(" ")
}
foreach($viewObjInfo in ($global:viewObjects | Where { $_.ViewInfo.AuthenticationID -eq "MSAL" }))
{
$viewObjInfo = $global:viewObjects | Where { $_.ViewInfo.Id -eq $global:EMViewObject.Id }
@@ -1262,10 +1345,8 @@ function Invoke-MSALCheckObjectViewAccess
{
if($authToken)
{
$accessToken = Get-JWTtoken $authToken.AccessToken
if($accessToken.Payload.scp)
if($curPermissions)
{
$curPermissions = $accessToken.Payload.scp.Split(" ")
foreach($viewItem in $viewObjInfo.ViewItems)
{
$full = 0
@@ -1563,6 +1644,10 @@ function Get-MSALProfileEllipse
{
$initials = "$($global:me.userPrincipalName[0])".ToUpper()
}
elseif($script:jwtAccessToken.Payload.idtyp -eq "app")
{
$initials = "APP"
}
$grd = Get-MSALUserPhotoEllips -size $size -fontSize $fontSize -Color $Color
@@ -1586,8 +1671,15 @@ function Get-MSALProfileEllipse
$global:grdProfileInfo.Tag = $grd
$grd.Tag = $global:grdProfileInfo
Set-XamlProperty $global:grdProfileInfo "txtOrganization" "Text" $global:Organization.displayName
Set-XamlProperty $global:grdProfileInfo "txtUsername" "Text" $global:me.displayName
Set-XamlProperty $global:grdProfileInfo "txtLogonName" "Text" $global:me.userPrincipalName
if($script:jwtAccessToken.Payload.idtyp -eq "app")
{
Set-XamlProperty $global:grdProfileInfo "txtUsername" "Text" "App Login"
}
else
{
Set-XamlProperty $global:grdProfileInfo "txtUsername" "Text" $global:me.displayName
Set-XamlProperty $global:grdProfileInfo "txtLogonName" "Text" $global:me.userPrincipalName
}
$global:tokenInfo = Get-JWTtoken $global:MSALToken.AccessToken
if($global:tokenInfo)
@@ -1612,140 +1704,148 @@ function Get-MSALProfileEllipse
$profileGrid.Children.Add($tmpObj) | Out-Null
}
if($script:jwtAccessToken.Payload.idtyp -eq "app")
{
$tmpObj.Visibility = "Collapsed"
}
$global:grdProfileInfo.Add_Loaded({param($obj, $e)
$point = $obj.Tag.TransformToAncestor($window).Transform([System.Windows.Point]::new(0,0));
[System.Windows.Controls.Canvas]::SetLeft($obj,($point.X - $obj.ActualWidth + $obj.Tag.ActualWidth))
[System.Windows.Controls.Canvas]::SetTop($obj,($point.Y + $obj.Tag.ActualHeight))
})
#########################################################################################################
### Show / Hide consent button
#########################################################################################################
$script:userEllipsGrid = $tmpObj
if(($script:missingPermissions | measure).Count -eq 0)
if($script:jwtAccessToken.Payload.idtyp -ne "app")
{
Set-XamlProperty $script:userEllipsGrid "lnkRequestConsent" "Visibility" "Collapsed"
}
Add-XamlEvent $script:userEllipsGrid "lnkRequestConsent" "add_Click" {
Start-MSALConsentPrompt
}
$otherLogins = $global:grdProfileInfo.FindName("grdCachedAccounts")
#########################################################################################################
### Add cached users
#########################################################################################################
if((Get-SettingValue "SortAccountList") -eq $true)
{
$accounts = $global:MSALAccounts | Sort -Property Username
}
else
{
$accounts = $global:MSALAccounts
}
foreach($account in $accounts)
{
# Skip current logged on user
if($global:MSALToken.Account.Username -eq $Account.Username -or
$global:MSALToken.Account.HomeAccountId.ObjectId -eq $Account.HomeAccountId.ObjectId) { continue }
Add-CachedUser $account $otherLogins
}
#########################################################################################################
### Add login with another user
#########################################################################################################
$grdAccount = [System.Windows.Controls.Grid]::new()
$cd = [System.Windows.Controls.ColumnDefinition]::new()
$grdAccount.ColumnDefinitions.Add($cd)
$cd = [System.Windows.Controls.ColumnDefinition]::new()
$cd.Width = [double]::NaN
$grdAccount.ColumnDefinitions.Add($cd)
$icon = Get-XamlObject ($global:AppRootFolder + "\Xaml\Icons\Logon.xaml")
$icon.Width = 24
$icon.Height = 24
$icon.Margin = "0,0,5,0"
$grdAccount.Children.Add($icon) | Out-Null
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS>Sign in with a different account</TextBlock>")
$lbObj.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
#$lbObj.Style = $window.TryFindResource("HoverUnderlineStyle")
$grdAccount.Children.Add($lbObj) | Out-Null
$lnkButton = [System.Windows.Controls.Button]::new()
$lnkButton.Content = $grdAccount
$lnkButton.Style = $window.TryFindResource("LinkButton")
$lnkButton.Margin = "0,5,0,0"
$lnkButton.Cursor = "Hand"
$lnkButton.Tag = $account
$lnkButton.add_Click({
Write-Status "Logging in..."
Hide-Popup
Connect-MSALUser -Interactive -ShowMenu
if($global:curObjectType)
#########################################################################################################
### Show / Hide consent button
#########################################################################################################
$script:userEllipsGrid = $tmpObj
if(($script:missingPermissions | measure).Count -eq 0)
{
Show-GraphObjects
Set-XamlProperty $script:userEllipsGrid "lnkRequestConsent" "Visibility" "Collapsed"
}
Add-XamlEvent $script:userEllipsGrid "lnkRequestConsent" "add_Click" {
Start-MSALConsentPrompt
}
Write-Status ""
})
$otherLogins = $global:grdProfileInfo.FindName("grdLoginAccount")
$otherLogins = $global:grdProfileInfo.FindName("grdCachedAccounts")
Add-GridObject $otherLogins $lnkButton
$otherLogins = $global:grdProfileInfo.FindName("grdTenantAccounts")
if(($script:AccessableTenants | measure).Count -gt 1)
{
#########################################################################################################
### Add switch to another tenant
### Add cached users
#########################################################################################################
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS><Bold>Tenants:</Bold></TextBlock>")
$lbObj.Margin = "0,5,0,0"
Add-GridObject $otherLogins $lbObj
foreach($tenant in $script:AccessableTenants)
if((Get-SettingValue "SortAccountList") -eq $true)
{
try
$accounts = $global:MSALAccounts | Sort -Property Username
}
else
{
$accounts = $global:MSALAccounts
}
foreach($account in $accounts)
{
# Skip current logged on user
if($global:MSALToken.Account.Username -eq $Account.Username -or
$global:MSALToken.Account.HomeAccountId.ObjectId -eq $Account.HomeAccountId.ObjectId) { continue }
Add-CachedUser $account $otherLogins
}
#########################################################################################################
### Add login with another user
#########################################################################################################
$grdAccount = [System.Windows.Controls.Grid]::new()
$cd = [System.Windows.Controls.ColumnDefinition]::new()
$grdAccount.ColumnDefinitions.Add($cd)
$cd = [System.Windows.Controls.ColumnDefinition]::new()
$cd.Width = [double]::NaN
$grdAccount.ColumnDefinitions.Add($cd)
$icon = Get-XamlObject ($global:AppRootFolder + "\Xaml\Icons\Logon.xaml")
$icon.Width = 24
$icon.Height = 24
$icon.Margin = "0,0,5,0"
$grdAccount.Children.Add($icon) | Out-Null
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS>Sign in with a different account</TextBlock>")
$lbObj.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
#$lbObj.Style = $window.TryFindResource("HoverUnderlineStyle")
$grdAccount.Children.Add($lbObj) | Out-Null
$lnkButton = [System.Windows.Controls.Button]::new()
$lnkButton.Content = $grdAccount
$lnkButton.Style = $window.TryFindResource("LinkButton")
$lnkButton.Margin = "0,5,0,0"
$lnkButton.Cursor = "Hand"
$lnkButton.Tag = $account
$lnkButton.add_Click({
Write-Status "Logging in..."
Hide-Popup
Connect-MSALUser -Interactive -ShowMenu
if($global:curObjectType)
{
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS HorizontalAlignment=`"Stretch`"><Bold>$($tenant.DisplayName)</Bold><LineBreak/>$($tenant.defaultDomain)<LineBreak/>$($tenant.tenantId)</TextBlock>")
Show-GraphObjects
}
Write-Status ""
})
if($tenant.tenantId -ne $global:MSALToken.TenantId)
$otherLogins = $global:grdProfileInfo.FindName("grdLoginAccount")
Add-GridObject $otherLogins $lnkButton
$otherLogins = $global:grdProfileInfo.FindName("grdTenantAccounts")
if(($script:AccessableTenants | measure).Count -gt 1)
{
#########################################################################################################
### Add switch to another tenant
#########################################################################################################
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS><Bold>Tenants:</Bold></TextBlock>")
$lbObj.Margin = "0,5,0,0"
Add-GridObject $otherLogins $lbObj
foreach($tenant in $script:AccessableTenants)
{
try
{
$lbObj.Style = $window.TryFindResource("HoverUnderlineStyleWithBackground")
$lbObj.HorizontalAlignment = "Stretch"
$lnkButton = [System.Windows.Controls.Button]::new()
$lnkButton.Content = $lbObj
$lnkButton.HorizontalAlignment = "Stretch"
$lnkButton.Style = $window.TryFindResource("ContentButton")
$lnkButton.Margin = "0,5,0,0"
$lnkButton.Cursor = "Hand"
$lnkButton.Tag = $tenant
$lnkButton.add_Click({
Write-Status "Logging in to $($this.Tag.DisplayName)"
# Set authority to selected tenant
$global:MSALTenantId = $this.Tag.tenantId
Hide-Popup
Connect-MSALUser -Account ($global:MSALAccounts | Where UserName -eq $global:MSALToken.Account.Username)
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $wpfNS HorizontalAlignment=`"Stretch`"><Bold>$($tenant.DisplayName)</Bold><LineBreak/>$($tenant.defaultDomain)<LineBreak/>$($tenant.tenantId)</TextBlock>")
if($global:curObjectType)
{
Show-GraphObjects
}
Write-Status ""
})
Add-GridObject $otherLogins $lnkButton
}
else
{
$lbObj.Background = $window.TryFindResource("SelectedRowBackgroundColor")
$lbObj.Margin = "0,5,0,0"
Add-GridObject $otherLogins $lbObj
if($tenant.tenantId -ne $global:MSALToken.TenantId)
{
$lbObj.Style = $window.TryFindResource("HoverUnderlineStyleWithBackground")
$lbObj.HorizontalAlignment = "Stretch"
$lnkButton = [System.Windows.Controls.Button]::new()
$lnkButton.Content = $lbObj
$lnkButton.HorizontalAlignment = "Stretch"
$lnkButton.Style = $window.TryFindResource("ContentButton")
$lnkButton.Margin = "0,5,0,0"
$lnkButton.Cursor = "Hand"
$lnkButton.Tag = $tenant
$lnkButton.add_Click({
Write-Status "Logging in to $($this.Tag.DisplayName)"
# Set authority to selected tenant
$global:MSALTenantId = $this.Tag.tenantId
Hide-Popup
Connect-MSALUser -Account ($global:MSALAccounts | Where UserName -eq $global:MSALToken.Account.Username)
if($global:curObjectType)
{
Show-GraphObjects
}
Write-Status ""
})
Add-GridObject $otherLogins $lnkButton
}
else
{
$lbObj.Background = $window.TryFindResource("SelectedRowBackgroundColor")
$lbObj.Margin = "0,5,0,0"
Add-GridObject $otherLogins $lbObj
}
}
catch {}
}
catch {}
}
}
@@ -1808,6 +1908,11 @@ function Get-MSALProfileEllipse
Show-GraphObjects
}
}
if($script:jwtAccessToken.Payload.idtyp -eq "app")
{
Set-XamlProperty $tmpObj "lnkLogout" "Visibility" "Collapsed"
}
}
catch {
Write-LogError "Failed to create profile information object. Error: " $_.Exception
@@ -1983,12 +2088,21 @@ function Get-MSALMissingScopes
$script:missingPermissions = @()
if($script:jwtAccessToken.Payload.idtyp -eq "app")
{
$curScopes = $script:jwtAccessToken.Payload.roles
}
else
{
$curScopes = $authToken.Scopes
}
foreach($scope in $reqScopes)
{
$tmpScope = $scope.Split('/')[-1]
if($tmpScope -eq ".default") { continue }
if($authToken.Scopes -contains $tmpScope) { continue }
if(($authToken.Scopes -like "*/$tmpScope")) { continue }
if($curScopes -contains $tmpScope) { continue }
if(($curScopes -like "*/$tmpScope")) { continue }
$arrTemp = $tmpScope.Split(".")
if($arrTemp[1] -eq "Read")
{
@@ -2012,6 +2126,9 @@ function Show-MSALDecodedToken {
$tokenData,
$title
)
if(-not $tokenData.Header) { return }
$tokenArr = @()
foreach($prop in ($tokenData.Header | GM | Where MemberType -eq NoteProperty))
{
+153 -53
View File
@@ -10,7 +10,7 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob
#>
function Get-ModuleVersion
{
'3.9.1'
'3.9.5'
}
$global:MSGraphGlobalApps = @(
@@ -183,6 +183,14 @@ function Invoke-InitializeModule
Description = "Certificate for Azure App"
}) "GraphSilent"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Login with App in UI (Preview)"
Key = "GraphAzureAppLogin"
Type = "Boolean"
DefaultValue = $false
Description = "Login with specified app in the UI. Note: Change will require app restart"
}) "GraphSilent"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Refresh Objects after copy"
Key = "RefreshObjectsAfterCopy"
@@ -214,6 +222,15 @@ function Invoke-InitializeModule
DefaultValue = $false
Description = "Expand assignments when listing objects. This can be used in custom columns based on assignment info"
}) "GraphGeneral"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Use Graph 1.0 (Not Recommended)"
Key = "UseGraphV1"
Type = "Boolean"
DefaultValue = $false
Description = "This will use production verionof graph, v1.0. Note: Thot officially supported since this can have unpredicted results. Some parts will require Beta version of Graph."
}) "GraphGeneral"
}
function Get-GraphAppInfo
@@ -261,6 +278,7 @@ function Invoke-GraphAuthenticationUpdated
$global:MigrationTableCacheId = $null
$global:LoadedDependencyObjects = $null
$global:migFileObj = $null
$global:AADObjectCache = $null
}
function Invoke-SettingsUpdated
@@ -270,7 +288,7 @@ function Invoke-SettingsUpdated
function Initialize-GraphSettings
{
$script:defaultVersion = ""
}
function Invoke-GraphRequest
@@ -297,7 +315,7 @@ function Invoke-GraphRequest
$ODataMetadata = "full", # full, minimal, none or skip
[ValidateSet("beta","v1.0")]
$GraphVersion = "beta",
$GraphVersion = "",
[switch]
$AllPages,
@@ -317,6 +335,22 @@ function Invoke-GraphRequest
Connect-MSALUser
}
if(-not $GraphVersion)
{
if(-not $script:defaultVersion)
{
if((Get-SettingValue "UseGraphV1") -eq $true)
{
$script:defaultVersion = "v1.0"
}
else
{
$script:defaultVersion = "beta"
}
}
$GraphVersion = $script:defaultVersion
}
$params = @{}
$requestId = [Guid]::NewGuid().guid
@@ -550,7 +584,7 @@ function Get-GraphObjects
if($SinglePage -eq $true)
{
#Use default page size or use below for a specific page size for testing
#$params.Add("pageSize",10) #!!!
#$params.Add("pageSize",5) #!!!
}
elseif($SingleObject -ne $true -and $SinglePage -ne $true)
{
@@ -1036,13 +1070,14 @@ function Get-GraphMetaData
$wc = New-Object System.Net.WebClient
$wc.Encoding = [System.Text.Encoding]::UTF8
$proxyURI = Get-ProxyURI
if($proxyURI)
{
$wc.Proxy = $proxyURI
}
try
{
if($proxyURI)
{
$wc.Proxy = [System.Net.WebProxy]::new($proxyURI)
}
[xml]$global:metaDataXML = $wc.DownloadString($url)
# Download to string and then use Save to format the XML output
$global:metaDataXML.Save($fi.FullName)
@@ -1056,6 +1091,16 @@ function Get-GraphMetaData
$wc.Dispose()
}
}
if(-not $global:metaDataXML -and $fi.Exists)
{
Write-Log "Using old version of Graph MetaData file" 2
try
{
[xml]$global:metaDataXML = Get-Content $fi.FullName
}
catch { }
}
}
}
@@ -1376,8 +1421,8 @@ function Start-GraphObjectExport
Write-Log "Start bulk export"
Write-Log "****************************************************************"
$tmpFolder = Expand-FileName (Get-XamlProperty $script:exportForm "txtExportPath" "Text")
Write-Log "Export root folder: $tmpFolder"
$script:exportRoot = Expand-FileName (Get-XamlProperty $script:exportForm "txtExportPath" "Text")
Write-Log "Export root folder: $script:exportRoot"
$global:AADObjectCache = $null
@@ -1391,10 +1436,11 @@ function Start-GraphObjectExport
$txtNameFilter = $global:txtExportNameFilter.Text.Trim()
Save-Setting "" "ExportNameFilter" $txtNameFilter
if($txtNameFilter) { Write-Log "Name filter: $txtNameFilter" }
try
{
$folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:exportForm "txtExportPath" "Text") (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
$folder = Get-GraphObjectFolder $item.ObjectType $script:exportRoot (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
$folder = Expand-FileName $folder
@@ -1684,6 +1730,7 @@ function Show-GraphImportForm
$importedObjectsCurType = 0
$navigationPropObjects = @()
$arrImportedObjects = @()
foreach ($fileObj in $filesToImport)
{
if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport" -and (Reset-GraphObject $fileObj $global:dgObjects.ItemsSource))
@@ -1699,9 +1746,15 @@ function Show-GraphImportForm
ImportedObject = $importedObj
}
}
$arrImportedObjects += $importedObj
$importedObjectsCurType++
}
if($global:curObjectType.PostFilesImportCommand)
{
& $global:curObjectType.PostFilesImportCommand $global:curObjectType $arrImportedObjects $filesToImport
}
if($importedObjectsCurType -gt 0 -and $global:LoadedDependencyObjects -is [HashTable] -and $global:LoadedDependencyObjects.ContainsKey($global:curObjectType.Id))
{
Write-Log "Remove $($global:curObjectType.Title) from dependency cache"
@@ -1870,7 +1923,6 @@ function Show-GraphBulkImportForm
function Start-GraphObjectImport
{
Write-Status "Import objects" -Block
Write-Log "****************************************************************"
Write-Log "Start bulk import"
@@ -1920,6 +1972,8 @@ function Start-GraphObjectImport
$importedObjectsCurType = 0
$arrImportedObjects = @()
foreach ($fileObj in @($filesToImport))
{
$objName = Get-GraphObjectName $fileObj.Object $item.ObjectType
@@ -1943,11 +1997,17 @@ function Start-GraphObjectImport
ImportedObject = $importedObj
}
}
$arrImportedObjects = $importedObj
$importedObjects++
$importedObjectsCurType++
}
if($item.ObjectType.PostFilesImportCommand)
{
& $item.ObjectType.PostFilesImportCommand $item.ObjectType $arrImportedObjects $filesToImport
}
if($importedObjectsCurType -gt 0 -and $global:LoadedDependencyObjects -is [HashTable] -and $global:LoadedDependencyObjects.ContainsKey($item.ObjectType.Id))
{
Write-Log "Remove $($item.ObjectType.Title) from dependency cache"
@@ -2243,6 +2303,11 @@ function Import-GraphFile
Import-GraphObjectAssignment $newObj $file.ObjectType $objClone.Assignments $file.FileInfo.FullName | Out-Null
}
if($newObj)
{
$file | Add-Member -NotePropertyName "ImportedObject" -NotePropertyValue $newObj
}
if($PassThru -eq $true -and $newObj)
{
$newObj
@@ -2637,7 +2702,7 @@ function Add-GroupMigrationObject
if(-not $groupId) { return }
$path = Get-GraphMigrationTableFile $global:txtExportPath.Text
$path = Get-GraphMigrationTableFile $script:ExportRoot
if(-not $path) { return }
@@ -2678,7 +2743,7 @@ function Add-GraphMigrationObject
if(-not $objId) { return }
$path = Get-GraphMigrationTableFile $global:txtExportPath.Text
$path = Get-GraphMigrationTableFile $script:ExportRoot
if(-not $path) { return }
@@ -2686,7 +2751,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
@@ -2712,7 +2777,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
}
}
@@ -3018,7 +3084,7 @@ function Add-GraphDependencyObjects
$url = "$($url.Trim())&$($depObjectType.QUERYLIST.Trim())"
}
$depObjects = (Invoke-GraphRequest $url -ODataMetadata "none").Value
$depObjects = (Invoke-GraphRequest $url -ODataMetadata "none" -AllPages).Value
$arrDepObjects = @()
foreach($depObject in $depObjects)
{
@@ -3072,8 +3138,8 @@ function Export-GraphObjects
$objectType = $global:curObjectType
Write-Status "Export $($objectType.Title)"
$global:ExportRoot = (Get-XamlProperty $script:exportForm "txtExportPath" "Text")
$folder = Get-GraphObjectFolder $objectType $global:ExportRoot (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
$script:ExportRoot = (Get-XamlProperty $script:exportForm "txtExportPath" "Text")
$folder = Get-GraphObjectFolder $objectType $script:ExportRoot (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
$folder = Expand-FileName $folder
@@ -3104,7 +3170,7 @@ function Export-GraphObjects
}
Save-Setting "" "LastUsedFullPath" $folder
Save-Setting "" "LastUsedRoot" $global:ExportRoot
Save-Setting "" "LastUsedRoot" $script:ExportRoot
Write-Status ""
}
@@ -3146,7 +3212,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"
}
@@ -3384,10 +3450,9 @@ function Get-GraphBatchObjects
{
param($objects, $txtNameFilter)
$curBatch = 1
$batchResults = @()
$batchArr = @()
$batchTotal = 0
$skipped = 0
$objectType = $null
foreach($obj in $objects)
@@ -3397,7 +3462,7 @@ function Get-GraphBatchObjects
if($objName -and $txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
{
$batchTotal++
$skipped++
}
else
{
@@ -3409,17 +3474,59 @@ function Get-GraphBatchObjects
headers = @{"Accept"="application/json;odata.metadata=$ometadata"}
}
}
}
if($batchArr.Count -eq 20 -or ($batchTotal + $batchArr.Count -eq $objects.Count))
if($batchArr.Count -eq 0) { return }
$batchResults = @((Invoke-GraphBatchRequest $batchArr $objectType.Title).body)
if(($batchResults | measure).Count -ne ($objects.Count - $skipped))
{
Write-Log "Not all batch objects returned. Expected $($objects.Count - $skipped) but only got $(($batchResults | measure).Count)"
}
if($objectType -and ($batchResults | measure).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
@@ -3428,10 +3535,11 @@ function Get-GraphBatchObjects
$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)
@@ -3448,11 +3556,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)
@@ -3469,7 +3585,7 @@ function Get-GraphBatchObjects
$retry = $true
$tmpBatchObj = [PSCustomObject]@{
requests = $retryArr
}
}
$json = $tmpBatchObj | ConvertTo-Json -Depth 50
Start-Sleep -Seconds $retryAfter
}
@@ -3481,27 +3597,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
}
@@ -4309,7 +4409,7 @@ function Save-GraphObjectToFile
function Get-GraphObjectFile
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
$fileName = (Get-GraphObjectName $obj $objectType).Trim('.')
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
+157
View File
@@ -1,4 +1,161 @@
# Release Notes
## 3.9.5 - 2024-01-20
**Fixes**
- **Import/Export**<br />
- Assignments were not exported for some policies with trailing . in the name<br />
Based on [Issue 184](https://github.com/Micke-K/IntuneManagement/issues/184)<br />
**NOTE:** Policy will not export if full path is over 260 characters<br />
- Fixed issue with policies not being exported when Batch was enabled in Settings<br />
and there was only one policy for the specified object type<br />
- Failed to get App Protection policies when Proxy was configured<br />
- Fixed issue with importing policies with dependency in tenants with 100+ policies for a single policy type<br />
Dependency only imported first page. All pages will be imported now to resolve dependencies<br />
Based on [Issue 183](https://github.com/Micke-K/IntuneManagement/issues/183)<br />
- Fixed issue with multiple export folders when using %DateTime% in path<br />
Based on [Issue 189](https://github.com/Micke-K/IntuneManagement/issues/189)<br />
- **Get Assignment Filter usage**<br />
- Filters not returned if only assigned to one policy<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 />
- **Compare**<br />
- Comparing Settings Catalog objects with exported objects failed<br />
Issue cause by offline documentation was not working<br />
Based on [Issue 183](https://github.com/Micke-K/IntuneManagement/issues/183)<br />
- **Documentation**<br />
- Offline documentation of Settings Catalog was not working.<br />
Values were always documented from online object<br />
- Conditional Access documentation updates for Android and iOS<br />
- App Protection documentation updates for Android and iOS<br />
- Language files re-generated. Azure shou now be Entra for some documentations.<br />
<br />
## 3.9.4 - 2023-12-18
**Fixes**
- **Get Assignment Filter usage**<br />
- All policies that supports filter should now be collected<br />
Please create an issue if not all expected filters are listed<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 />
- **Documentation**<br />
- Added support for documenting Conditional Access policies based on Workloads<br />
Not 100% tested. Please report if not documented correctly<br />
<br />
## 3.9.3 - 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**
- **Application Content Export - Experimental**<br />
- Added support for Exporting Appliction with decrypted content<br />
App file can be downloaded during export or from the detail view of the Application<br />
Enable "Save Encryption File" and specify "App download folder" in Settings<br />
"App download folder" is used for encryption file and manual download<br />
File content will be downloaded to the export foler during export<br />
Files will be downloaded with .encrypted extension and then decrypted to original file name<br />
Please report any issue or any suggestions<br />
**NOTE:** This will ONLY work if the encryption file is exported and available<br />
- **Authentication**<br />
- Login with application<br />
This will login with specified Azure App ID and Secret/Certificate that is used for Batch processes<br />
NOTE: This will require a restart of the app<br />
Start with app **must** use -TenantID on command line. AppID and Secret/Certificate can be specified in Settings or command line<br />
Example: Start-IntuneManagement.ps1 -tenantId \"&lt;TenantID&gt;\" -appid \"&lt;AppID&gt;\" -secret \"&lt;Secret&gt;\"<br />
See *Start-WithApp.cmd* for samle file<br />
Based on [Issue 122](https://github.com/Micke-K/IntuneManagement/issues/122) and [Issue 134](https://github.com/Micke-K/IntuneManagement/issues/134)<br />
- **Support for new Settings**<br />
- Save encryption file - Saves a json file with encryption data when an application file is uploaded eg created or uploaded in details view<br />
- App download folder - Folder where application files should be downloaded and decrypted<br />
- Login with App in UI (Preview) - Use app batch login in UI<br />
- Use Graph 1.0 (Not Recommended) - Use Graph v1.0 instead of Beta. **Note:** Some features will NOT work in v1.0<br />
Based on [Issue 170](https://github.com/Micke-K/IntuneManagement/issues/170)<br />
**Fixes**
- **Documentation**<br />
- Language files re-generated eg Supersedence (preview) -> Supersedence<br />
- Added support for documenting "Filter for devices" info for Conditional Access policies<br />
Based on [Issue 168](https://github.com/Micke-K/IntuneManagement/issues/168)<br />
- **Custom ADMX Files**<br />
- Fixed issues with migrating custom policies between environments (3rd time)<br />
Based on [Issue 124](https://github.com/Micke-K/IntuneManagement/issues/124)<br />
- Fixed issue when importing ADMX files - Encoding issue eg ADMX/ADML file was UTF8<br />
Based on [Issue 169](https://github.com/Micke-K/IntuneManagement/issues/169)<br />
- **Importing Windows LoB Apps**<br />
- Fixed issue when importing LoB Apps that was only targeted to System context<br />
Available Assignment option was missing after import<br />
Based on [Discussion 164](https://github.com/Micke-K/IntuneManagement/discussions/164)<br />
- Added support for Depnedency and Supersedence reations at import<br />
Application will need to be re-exported since additinal data is added to the export file<br />
Based on [Discussion 159](https://github.com/Micke-K/IntuneManagement/discussions/159)<br />
- **Generic**<br />
- Fixed issue when compiling Procxy CS file<br />
- Tls 1.2 is now enforced.<br />
Based on [Discussion 166](https://github.com/Micke-K/IntuneManagement/discussions/166)<br />
<br />
## 3.9.1 - 2023-08-30
**New features**
+159
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
+1
View File
@@ -0,0 +1 @@
cmd /c powershell -version 5 -ex bypass -File "%~DP0Start-IntuneManagement.ps1" -tenantId "<TenantID>" -appid "<AppID>" -secret "<Secret>"
@@ -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"
+54
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>
+1 -1
View File
@@ -135,7 +135,7 @@
<Label Content="Current settings:" />
<Rectangle Style="{DynamicResource InfoIcon}" ToolTip="Current property settings. Only updated when saved" />
</StackPanel>
<Label Name="lblObjectColumnsConfig" Content="" Margin="0,0,5,0" Grid.Row="3" />
<Label Name="lblObjectColumnsConfig" Content="" Margin="0,0,5,0" Grid.Row="3" Grid.ColumnSpan="2" />
<StackPanel Orientation="Horizontal" Grid.Column="1" Margin="5,5,5,0" Grid.Row="4" Grid.ColumnSpan="2" HorizontalAlignment="Right">
<Button Name="btnObjectColumnsReset" Content="Reset" Margin="0,0,5,0" Width="100" ToolTip="Revert all changes" />