diff --git a/.gitignore b/.gitignore index 9348176..2ef597a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ .vs/ .vscode/ .git/ -/CloudAPIPowerShellManagement.Log -/CloudAPIPowerShellManagement.Lo_ -/Documentation/Get-LanguageStrings.ps1 +/*.Log +/*.Lo_ /*.csv diff --git a/Core.psm1 b/Core.psm1 index 1ec03d0..56d8e1d 100644 --- a/Core.psm1 +++ b/Core.psm1 @@ -12,7 +12,7 @@ This module handles the WPF UI function Get-ModuleVersion { - '3.1.5' + '3.1.6' } function Start-CoreApp @@ -484,7 +484,7 @@ function Show-UpdatesDialog $content = Invoke-RestMethod "https://api.github.com/repos/Micke-K/IntuneManagement/contents/ReleaseNotes.md" if($content) { - $txt = [System.Text.Encoding]::ASCII.GetString(([System.Convert]::FromBase64String($content.content))) + $txt = [System.Text.Encoding]::UTF8.GetString(([System.Convert]::FromBase64String($content.content))) Set-XamlProperty $script:dlgUpdates "txtReleaseNotes" "Text" $txt if($content.sha -ne $curHash.Hash) @@ -1528,8 +1528,8 @@ function Get-JWTtoken while ($payload.Length % 4) { $payload += "=" } # Add padding to match required length return (New-Object PSObject -Property @{ - Header=(([System.Text.Encoding]::ASCII.GetString(([System.Convert]::FromBase64String($header)))) | ConvertFrom-Json) - Payload=(([System.Text.Encoding]::ASCII.GetString(([System.Convert]::FromBase64String($payload)))) | ConvertFrom-Json) + Header=(([System.Text.Encoding]::UTF8.GetString(([System.Convert]::FromBase64String($header)))) | ConvertFrom-Json) + Payload=(([System.Text.Encoding]::UTF8.GetString(([System.Convert]::FromBase64String($payload)))) | ConvertFrom-Json) }) } #endregion diff --git a/Documentation.md b/Documentation.md index df14af9..16577bb 100644 --- a/Documentation.md +++ b/Documentation.md @@ -109,7 +109,7 @@ These files will have to be re-generated when new functionality is released in I **Scripts for Generated Files** -The scripts that automatically generates language files, translation files, object info etc. are not included in the release. These scripts are currently not in a state that they can be released. The best would be if Microsoft released all the required information in Graph. A deep dive into graph suggests that it might be possible in the future since some information about the generated files are there but with some properties missing or missing. The information can't be accessed unless an API is called that gets the definition for all the profiles at the same time. +The scripts that automatically generates language files, translation files, object info etc. are not included in the release. These scripts are currently not in a state that they can be released. The best would be if Microsoft released all the required information in Graph. A deep dive into graph suggests that it might be possible in the future since some information about the generated files are there but with some properties missing or language text missing. The information can't be accessed unless an API is called that gets the definition for all the profiles at the same time (the file is over 100MB). ## Extending The Documentation @@ -130,7 +130,7 @@ The priority order for object documentation is: **Documentation Provider** -The documentation provider takes care of the collection information about the object. The `DocumentationCustom.psm1`file is an example of this. This file has examples of custom translation of properties for json files and examples of custom translation of objects via a PowerShell functions. +The documentation provider takes care of collecting all the information about the object. The `DocumentationCustom.psm1`file is an example of this. This file has examples of custom translation of properties for json files and examples of custom translation of objects via a PowerShell functions. Documentation providers has a Priority property. This defines in what order the providers will be triggered. The provider with the lowest priority number will be executed first. The included custom documentation provider has a priority number of 1000. The information gathering of the provider can be overridden by creating a custom documentation provider with a lower priority number. diff --git a/Documentation/ObjectInfo/#Applications.json b/Documentation/ObjectInfo/#Applications.json index 0a1c1b4..cad3288 100644 --- a/Documentation/ObjectInfo/#Applications.json +++ b/Documentation/ObjectInfo/#Applications.json @@ -351,6 +351,22 @@ ] } }, + { + "nameResourceKey": "OfficeSuiteAppsTab.xmlConfigurationLabel", + "descriptionResourceKey": "", + "entityKey": "MSAppsConfigXml", + "dataType": 20, + "booleanActions": 0, + "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", + "Condition": { + "Expressions": [ + { + "property": "@OData.Type", + "value": "#microsoft.graph.officeSuiteApp" + } + ] + } + }, { "nameResourceKey": "OfficeSuiteAppsTab.appsToBeInstalledLabel", "descriptionResourceKey": "OfficeSuiteAppsTab.selectOfficeAppsTooltip", @@ -393,7 +409,12 @@ } ], "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -419,7 +440,12 @@ } ], "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -433,7 +459,12 @@ "dataType": 8, "booleanActions": 0, "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -459,7 +490,12 @@ } ], "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -497,7 +533,12 @@ } ], "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -513,7 +554,12 @@ "booleanActions": 109, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -529,7 +575,12 @@ "booleanActions": 0, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -543,7 +594,12 @@ "dataType": 8, "booleanActions": 0, "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -559,7 +615,12 @@ "booleanActions": 109, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -575,7 +636,12 @@ "booleanActions": 109, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -591,7 +657,12 @@ "booleanActions": 109, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -607,7 +678,12 @@ "booleanActions": 0, "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" @@ -660,7 +736,12 @@ "category": "OfficeSuiteAppsTab.appSuiteConfigurationLabel", "unconfiguredValue": "os-default", "Condition": { + "type": "and", "Expressions": [ + { + "property": "MSAppsConfigXml", + "operator": "null" + }, { "property": "@OData.Type", "value": "#microsoft.graph.officeSuiteApp" diff --git a/Documentation/ObjectInfo/#microsoft.graph.deviceHealthScript.json b/Documentation/ObjectInfo/#microsoft.graph.deviceHealthScript.json new file mode 100644 index 0000000..3333713 --- /dev/null +++ b/Documentation/ObjectInfo/#microsoft.graph.deviceHealthScript.json @@ -0,0 +1,41 @@ +[ + { + "nameResourceKey": "ProactiveRemediations.Create.Settings.DetectionScriptMultiLineTextBox.label", + "descriptionResourceKey": "", + "entityKey": "detectionScriptAdded", + "dataType": 0, + "booleanActions": 109, + "category": "WindowsManagement.scriptsettingsTabHeader" + }, + { + "nameResourceKey": "ProactiveRemediations.Create.Settings.RemediationScriptMultiLineTextBox.label", + "descriptionResourceKey": "", + "entityKey": "remediationScriptAdded", + "dataType": 0, + "booleanActions": 109, + "category": "WindowsManagement.scriptsettingsTabHeader" + }, + { + "nameResourceKey": "ProactiveRemediations.Create.Settings.LoggedOnCredentialsOptionPicker.label", + "descriptionResourceKey": "", + "entityKey": "useLoggedOnCredentials", + "dataType": 0, + "booleanActions": 109 + }, + { + "nameResourceKey": "ProactiveRemediations.Create.Settings.EnforceScriptSignatureCheckOptionPicker.label", + "descriptionResourceKey": "", + "entityKey": "enforceSignatureCheck", + "dataType": 0, + "booleanActions": 109 + + }, + { + "nameResourceKey": "ProactiveRemediations.Create.Settings.RunIn64BitPSOptionPicker.label", + "descriptionResourceKey": "", + "entityKey": "runAs32Bit", + "dataType": 0, + "booleanActions": 110 + + } +] \ No newline at end of file diff --git a/Documentation/Strings-cs.json b/Documentation/Strings-cs.json index 0a5703c..a16a06d 100644 Binary files a/Documentation/Strings-cs.json and b/Documentation/Strings-cs.json differ diff --git a/Documentation/Strings-de.json b/Documentation/Strings-de.json index 7c78460..7490b7d 100644 Binary files a/Documentation/Strings-de.json and b/Documentation/Strings-de.json differ diff --git a/Documentation/Strings-en.json b/Documentation/Strings-en.json index 1866929..2f11810 100644 Binary files a/Documentation/Strings-en.json and b/Documentation/Strings-en.json differ diff --git a/Documentation/Strings-es.json b/Documentation/Strings-es.json index c2fb9ee..02a874d 100644 Binary files a/Documentation/Strings-es.json and b/Documentation/Strings-es.json differ diff --git a/Documentation/Strings-fr.json b/Documentation/Strings-fr.json index 083b609..d5738c7 100644 Binary files a/Documentation/Strings-fr.json and b/Documentation/Strings-fr.json differ diff --git a/Documentation/Strings-hu.json b/Documentation/Strings-hu.json index cf0f366..b3c52ee 100644 Binary files a/Documentation/Strings-hu.json and b/Documentation/Strings-hu.json differ diff --git a/Documentation/Strings-it.json b/Documentation/Strings-it.json index 0e95829..d8b3279 100644 Binary files a/Documentation/Strings-it.json and b/Documentation/Strings-it.json differ diff --git a/Documentation/Strings-ja.json b/Documentation/Strings-ja.json index cdf5eff..de69305 100644 Binary files a/Documentation/Strings-ja.json and b/Documentation/Strings-ja.json differ diff --git a/Documentation/Strings-ko.json b/Documentation/Strings-ko.json index a7260d8..5660692 100644 Binary files a/Documentation/Strings-ko.json and b/Documentation/Strings-ko.json differ diff --git a/Documentation/Strings-nl.json b/Documentation/Strings-nl.json index 96a5817..db1064f 100644 Binary files a/Documentation/Strings-nl.json and b/Documentation/Strings-nl.json differ diff --git a/Documentation/Strings-pl.json b/Documentation/Strings-pl.json index 771ccad..af8a8b7 100644 Binary files a/Documentation/Strings-pl.json and b/Documentation/Strings-pl.json differ diff --git a/Documentation/Strings-pt.json b/Documentation/Strings-pt.json index 1866929..2f11810 100644 Binary files a/Documentation/Strings-pt.json and b/Documentation/Strings-pt.json differ diff --git a/Documentation/Strings-ru.json b/Documentation/Strings-ru.json index d61d861..3a6b5f5 100644 Binary files a/Documentation/Strings-ru.json and b/Documentation/Strings-ru.json differ diff --git a/Documentation/Strings-sv.json b/Documentation/Strings-sv.json index 19f9677..5904ee5 100644 Binary files a/Documentation/Strings-sv.json and b/Documentation/Strings-sv.json differ diff --git a/Documentation/Strings-tr.json b/Documentation/Strings-tr.json index 316cd22..9eb88c3 100644 Binary files a/Documentation/Strings-tr.json and b/Documentation/Strings-tr.json differ diff --git a/Documentation/Strings-zh-chs.json b/Documentation/Strings-zh-chs.json index 1866929..2f11810 100644 Binary files a/Documentation/Strings-zh-chs.json and b/Documentation/Strings-zh-chs.json differ diff --git a/Documentation/Strings-zh-cht.json b/Documentation/Strings-zh-cht.json index 1866929..2f11810 100644 Binary files a/Documentation/Strings-zh-cht.json and b/Documentation/Strings-zh-cht.json differ diff --git a/Documentation/Strings-zh-hans.json b/Documentation/Strings-zh-hans.json index 8123632..fc673a4 100644 Binary files a/Documentation/Strings-zh-hans.json and b/Documentation/Strings-zh-hans.json differ diff --git a/Documentation/Strings-zh-hant.json b/Documentation/Strings-zh-hant.json index 661e0bc..6245d20 100644 Binary files a/Documentation/Strings-zh-hant.json and b/Documentation/Strings-zh-hant.json differ diff --git a/Documentation/Strings-zh.json b/Documentation/Strings-zh.json index 1866929..2f11810 100644 Binary files a/Documentation/Strings-zh.json and b/Documentation/Strings-zh.json differ diff --git a/Extensions/Compare.psm1 b/Extensions/Compare.psm1 index ab6a634..6e2daab 100644 --- a/Extensions/Compare.psm1 +++ b/Extensions/Compare.psm1 @@ -11,7 +11,7 @@ Objects can be compared based on Properties or Documentatation info. function Get-ModuleVersion { - '1.0.6' + '1.0.7' } function Invoke-InitializeModule @@ -399,8 +399,9 @@ function Invoke-BulkCompareNamedObjects } else { - # Add objects that are exported but deleted - Write-Log "Object '$((Get-GraphObjectName $graphObj.Object $graphObj.ObjectType))' with id $($graphObj.Object.Id) has no matching object with the compate pattern" 2 + $sourceObj = Get-GraphObject $graphObj.Object $graphObj.ObjectType + # Add objects that are exported but deleted/not imported etc. + Write-Log "Object '$((Get-GraphObjectName $graphObj.Object $graphObj.ObjectType))' with id $($graphObj.Object.Id) has no matching object with the compare pattern" 2 $compareProperties = @([PSCustomObject]@{ Object1Value = (Get-GraphObjectName $graphObj.Object $graphObj.ObjectType) Object2Value = $null @@ -828,7 +829,15 @@ function Compare-Objects $script:compareProperties = @() - if($global:cbCompareType.SelectedItem.Compare) + if($obj1.'@OData.Type' -eq "#microsoft.graph.deviceManagementConfigurationPolicy" -or + $obj1.'@OData.Type' -eq "#microsoft.graph.deviceManagementIntent" -or + $obj1.'@OData.Type' -eq "#microsoft.graph.groupPolicyConfiguration") + { + # Always use documentation for Settings Catalog, Endpoint Security and Administrative Template policies + # These use Graph API for docummentation and all properties will be documented + $compareResult = Compare-ObjectsBasedonDocumentation $obj1 $obj2 $objectType + } + elseif($global:cbCompareType.SelectedItem.Compare) { $compareResult = & $global:cbCompareType.SelectedItem.Compare $obj1 $obj2 $objectType } diff --git a/Extensions/Documentation.psm1 b/Extensions/Documentation.psm1 index bca0503..6ae6a82 100644 --- a/Extensions/Documentation.psm1 +++ b/Extensions/Documentation.psm1 @@ -20,7 +20,7 @@ $global:documentationProviders = @() function Get-ModuleVersion { - '1.0.6' + '1.0.7' } function Invoke-InitializeModule @@ -443,6 +443,10 @@ function Get-ObjectTypeString elseif($objTypeId -eq "TenantAdmin") { return (Get-LanguageString "Titles.tenantAdmin") + } + elseif($objTypeId -eq "Azure") + { + return "Azure" } } @@ -1600,10 +1604,14 @@ function Invoke-VerifyCondition return $false } - if($expression.value -eq $null) + if($expression.operator -eq "null") + { + $tmpRet = $null -eq $tmpProp.Value + } + elseif($null -eq $expression.value) { # Value not specified. Check if the property is set - $tmpRet = $tmpProp.Value -ne $null + $tmpRet = $null -ne $tmpProp.Value } elseif($expression.operator -eq "ne") { @@ -1737,7 +1745,7 @@ function Invoke-TranslateSection { $value = Get-LanguageString $prop.value - Add-PropertyInfo $prop $value $rawValue + Add-PropertyInfo $prop $value $rawValue $rawValue } } elseif([String]::IsNullOrEmpty($prop.entityKey) -eq $false) @@ -1791,6 +1799,7 @@ function Invoke-TranslateSection { $value = $cert.displayName } + $rawValue = $value } } elseif($prop.dataType -eq 200) # Multi option based on boolean value @@ -2063,7 +2072,7 @@ function Get-PropertyInfo $categoryStr = Get-Category $prop.category } - if(!$jsonValue -and $rawValue -ne $null -and "$($rawValue)" -ne "") + if(!$jsonValue -and $null -ne $rawValue -and "$($rawValue)" -ne "") { $jsonValue = $rawValue | ConvertTo-Json -Depth 10 -Compress } @@ -2436,7 +2445,7 @@ function Invoke-TranslateOption Value=$optionValue } - Add-PropertyInfo $prop $optionValue -originalValue $propValue + Add-PropertyInfo $prop $optionValue $propValue if($SkipOptionChildren -ne $true) { Invoke-ChildSections (Get-CustomChildObject $obj $prop) $option @@ -3138,6 +3147,8 @@ function Show-DocumentationForm { foreach($groupId in ($objectTypes | Select GroupId -Unique).GroupId) { + if(-not $groupId) { continue } + #$script:DocumentationLanguage = ?? $global:cbDocumentationLanguage.SelectedValue "en" $script:DocumentationLanguage = "en" $groupName = Get-ObjectTypeString -ObjectType $groupId @@ -3375,9 +3386,24 @@ function Show-DocumentationForm } $tmpCurObjectType = $null + $tmpCurObjectGroup = $null + $allObjectTypeObjects = @() foreach($tmpObj in ($sourceList)) { - $obj = Get-GraphObject $tmpObj.Object $tmpObj.ObjectType + if($allObjectTypeObjects.Count -gt 0 -and $tmpCurObjectGroup -ne $tmpObj.ObjectType.GroupId -and $tmpCurObjectType -ne $tmpObj.ObjectType.Id) + { + if($global:cbDocumentationType.SelectedItem.ProcessAllObjects) + { + & $global:cbDocumentationType.SelectedItem.ProcessAllObjects $allObjectTypeObjects + $allObjectTypeObjects = @() + } + else + { + Write-Log "ProcessAllObjects not defined. $tmpCurObjectType will not be documented" 3 + } + } + + $obj = Get-GraphObject $tmpObj.Object $tmpObj.ObjectType if($obj) { @@ -3398,19 +3424,35 @@ function Show-DocumentationForm { # The provider takes care of all the processing Write-Status "Run CustomProcess for $($global:cbDocumentationType.SelectedItem.Name)" - & $global:cbDocumentationType.SelectedItem.CustomProcess $obj $documentedObj - continue + $ret = & $global:cbDocumentationType.SelectedItem.CustomProcess $obj $documentedObj + if($ret -is [boolean] -and $ret -eq $true) { continue } } - if($tmpCurObjectType -ne $obj.ObjectType.GroupId) + if($tmpCurObjectGroup -ne $obj.ObjectType.GroupId) { + # A group matches a menu item in the protal but can contain multiple object types + # New object group e.g. Script, Tennant, Device Configuration + if($global:cbDocumentationType.SelectedItem.NewObjectGroup) + { + Write-Status "Run NewObjectGroup for $($global:cbDocumentationType.SelectedItem.Name)" + $ret = & $global:cbDocumentationType.SelectedItem.NewObjectGroup $obj $documentedObj + if($ret -is [boolean] -and $ret -eq $true) { continue } + } + $tmpCurObjectGroup = $obj.ObjectType.GroupId + } + + if($tmpCurObjectType -ne $obj.ObjectType.Id) + { + # New object type e.g Administrative Template, VPN profile etc. if($global:cbDocumentationType.SelectedItem.NewObjectType) { Write-Status "Run NewObjectType for $($global:cbDocumentationType.SelectedItem.Name)" - & $global:cbDocumentationType.SelectedItem.NewObjectType $obj $documentedObj + $ret = & $global:cbDocumentationType.SelectedItem.NewObjectType $obj $documentedObj + if($ret -is [boolean] -and $ret -eq $true) { continue } } - $tmpCurObjectType = $obj.ObjectType.GroupId - } + $tmpCurObjectType = $obj.ObjectType.Id + $allObjectTypeObjects = @() + } if($documentedObj) { @@ -3435,15 +3477,16 @@ function Show-DocumentationForm $filteredSettings = @() foreach($item in $documentedObj.Settings) { - if(-not ($item.PSObject.Properties | Where Name -eq RawValue) -or $documentedObj.UpdateFilteredObject -eq $false) + if(-not ($item.PSObject.Properties | Where Name -eq "RawValue") -or $documentedObj.UpdateFilteredObject -eq $false) { $filteredSettings = $documentedObj.Settings break } - if($global:chkSkipNotConfigured.IsChecked -and (([String]::IsNullOrEmpty($item.RawValue) -or ("$($item.RawValue)" -eq "notConfigured")))) + if($global:chkSkipNotConfigured.IsChecked -and (($item.RawValue -isnot [array] -and ([String]::IsNullOrEmpty($item.RawValue) -or ("$($item.RawValue)" -eq "notConfigured"))) -or ($item.RawValue -is [array] -and ($item.RawValue | measure).Count -eq 0))) { # Skip unconfigured items e.g. properties with null values + # Note: This could removed configured properties if RawValue is not specified continue } elseif($global:chkSkipNotConfigured.IsChecked -and $documentedObj.UnconfiguredProperties -and ($documentedObj.UnconfiguredProperties | Where EntityKey -eq $item.EntityKey)) @@ -3472,7 +3515,7 @@ function Show-DocumentationForm } } - if($updateNotConfigured -and ($item.RawValue -eq $null -or "$($item.RawValue)" -eq "" -or "$($item.RawValue)" -eq "notConfigured") -and [String]::IsNullOrEmpty($item.Value)) + if($updateNotConfigured -and (($item.RawValue -isnot [array] -and ($null -eq $item.RawValue -or "$($item.RawValue)" -eq "" -or "$($item.RawValue)" -eq "notConfigured") -and [String]::IsNullOrEmpty($item.Value)) -or ($item.RawValue -is [array] -and ($item.RawValue | measure).Count -eq 0))) { $item.Value = $notConfiguredText } @@ -3489,12 +3532,36 @@ function Show-DocumentationForm $documentedObj | Add-Member Noteproperty -Name "FilteredSettings" -Value $filteredSettings -Force - & $global:cbDocumentationType.SelectedItem.Process $obj.Object $obj.ObjectType $documentedObj + if($obj.ObjectType.DocumentAll -eq $true) + { + $allObjectTypeObjects += [PSCustomObject]@{ + Object = $obj + DocumentationObject = $documentedObj + } + } + else + { + & $global:cbDocumentationType.SelectedItem.Process $obj.Object $obj.ObjectType $documentedObj + } } } } } + if($allObjectTypeObjects.Count -gt 0) + { + if($global:cbDocumentationType.SelectedItem.ProcessAllObjects) + { + & $global:cbDocumentationType.SelectedItem.ProcessAllObjects $allObjectTypeObjects + $allObjectTypeObjects = @() + } + else + { + Write-Log "ProcessAllObjects not defined. $tmpCurObjectType will not be documented" 3 + } + } + + if($global:cbDocumentationType.SelectedItem.PostProcess) { Write-Status "Run PostProcess for $($global:cbDocumentationType.SelectedItem.Name)" diff --git a/Extensions/DocumentationCustom.psm1 b/Extensions/DocumentationCustom.psm1 index 58ebad2..e90114c 100644 --- a/Extensions/DocumentationCustom.psm1 +++ b/Extensions/DocumentationCustom.psm1 @@ -10,7 +10,7 @@ This module will also document some objects based on PowerShell functions function Get-ModuleVersion { - '1.0.4' + '1.0.5' } function Invoke-InitializeModule @@ -306,6 +306,20 @@ function Add-CDDocumentCustomProfileValue } } } + elseif($obj.'@OData.Type' -like "#microsoft.graph.windows10VpnConfiguration") + { + if($prop.EntityKey -eq "enableSplitTunneling" -and $prop.enabled -eq $false) + { + # SplitTunneling settings are moved to another file + return $false + } + elseif($prop.EntityKey -eq "eapXml" -and $obj.eapXml) + { + $propValue = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($obj.eapXml)) + Add-PropertyInfo $prop $propValue -originalValue $propValue + return $false + } + } } <# @@ -638,6 +652,11 @@ function Add-CDDocumentCustomProfileProperty $obj | Add-Member Noteproperty -Name "useMicrosoftSearchAsDefault" -Value ($obj.excludedApps.bing -eq $false) + if($obj.officeConfigurationXml) + { + $xmlConfig = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($obj.officeConfigurationXml)) + $obj | Add-Member Noteproperty -Name "MSAppsConfigXml" -Value $xmlConfig + } $retValue = $true } elseif($obj.'@OData.Type' -like "#microsoft.graph.windowsWifiEnterpriseEAPConfiguration") @@ -1037,6 +1056,12 @@ function Add-CDDocumentCustomProfileProperty $obj | Add-Member Noteproperty -Name "returnCodes" -Value ($returnCodes -join $objSeparator) -Force $obj | Add-Member Noteproperty -Name "win10Release" -Value (Get-LanguageString "MinimumOperatingSystem.Windows.V10Release.release$($obj.minimumSupportedWindowsRelease)") -Force } + elseif($obj.'@OData.Type' -eq "#microsoft.graph.deviceHealthScript") + { + $obj | Add-Member Noteproperty -Name "detectionScriptAdded" -Value (-not [String]::IsNullOrEmpty($obj.detectionScriptContent)) + $obj | Add-Member Noteproperty -Name "remediationScriptAdded" -Value (-not [String]::IsNullOrEmpty($obj.remediationScriptContent)) + $obj | Add-Member Noteproperty -Name "useLoggedOnCredentials" -Value ($obj.runAsAccount -ne "system") + } if(($obj.PSObject.Properties | where Name -eq "securityRequireSafetyNetAttestationBasicIntegrity") -and ($obj.PSObject.Properties | where Name -eq "securityRequireSafetyNetAttestationCertifiedDevice")) diff --git a/Extensions/DocumentationWord.psm1 b/Extensions/DocumentationWord.psm1 index 9fedef6..02b0042 100644 --- a/Extensions/DocumentationWord.psm1 +++ b/Extensions/DocumentationWord.psm1 @@ -3,7 +3,7 @@ #https://docs.microsoft.com/en-us/office/vba/api/overview/word function Get-ModuleVersion { - '1.0.3' + '1.0.4' } function Invoke-InitializeModule @@ -27,9 +27,10 @@ function Invoke-InitializeModule OutputOptions = (Add-WordOptionsControl) Activate = { Invoke-WordActivate @args } PreProcess = { Invoke-WordPreProcessItems @args } - NewObjectType = { Invoke-WordNewObjectType @args } + NewObjectGroup = { Invoke-WordNewObjectGroup @args } Process = { Invoke-WordProcessItem @args } PostProcess = { Invoke-WordPostProcessItems @args } + ProcessAllObjects = { Invoke-WordProcessAllObjects @args } }) $script:columnHeaders = @{ @@ -389,7 +390,7 @@ function Set-WordContentControlText } } -function Invoke-WordNewObjectType +function Invoke-WordNewObjectGroup { param($obj, $documentedObj) @@ -408,7 +409,7 @@ function Invoke-WordProcessItem Add-DocText $objName $global:txtWordHeader2Style.Text - $script:doc.Application.Selection.TypeParagraph() + $script:doc.Application.Selection.TypeParagraph() try { @@ -517,6 +518,45 @@ function Invoke-WordProcessItem } } +function Invoke-WordProcessAllObjects +{ + param($allObjectTypeObjects) + + if(($allObjectTypeObjects | measure).Count -eq 0) { return } + + $tmpObj = $allObjectTypeObjects | Select -First 1 + if(-not $tmpObj) { return } + + $objectType = $tmpObj.Object.ObjectType + if($objectType.Id -eq "ScopeTags") + { + $objTypeName = Get-LanguageString "SettingDetails.scopeTags" + + Add-DocText $objTypeName $global:txtWordHeader2Style.Text + + $script:doc.Application.Selection.TypeParagraph() + + $items = @() + + $nameLabel = Get-LanguageString "Inputs.displayNameLabel" + $descriptionLable = Get-LanguageString "TableHeaders.description" + foreach($obj in $allObjectTypeObjects.Object.Object) + { + $items += [PSCustomObject]@{ + $nameLabel = $obj.displayName + ID = $obj.Id + $descriptionLable = $obj.Description + } + } + + $items = $items | Sort -Property $nameLabel + + $properties = @($nameLabel,"id",$descriptionLable) + + Add-DocTableItems $tmpObj.Object.Object $tmpObj.Object.ObjectType $items $properties -captionOverride (Get-LanguageString "SettingDetails.scopeTags") + } +} + function Invoke-WordTranslateColumnHeader { param($columnName) @@ -529,6 +569,13 @@ function Invoke-WordTranslateColumnHeader (?? $lngText $columnName) } + +function Invoke-WordCustomProcessItems +{ + param($obj, $documentedObj) + +} + function Set-WordColumnHeaderLanguageId { param($columnName, $lngId) @@ -547,7 +594,7 @@ function Set-WordColumnHeaderLanguageId function Add-DocTableItems { - param($obj, $objectType, $items, $properties, $lngId, [switch]$AddCategories, [switch]$AddSubcategories) + param($obj, $objectType, $items, $properties, $lngId, [switch]$AddCategories, [switch]$AddSubcategories, $captionOverride) $tblHeaderStyle = $global:txtWordTableHeaderStyle.Text $tblCategoryStyle = $global:txtWordCategoryHeaderStyle.Text @@ -559,7 +606,11 @@ function Add-DocTableItems $script:docTable.ApplyStyleHeadingRows = $true Set-DocObjectStyle $script:docTable $global:txtWordTableStyle.Text - if($lngId) + if($captionOverride) + { + $caption = $captionOverride + } + elseif($lngId) { $caption = "$((Get-LanguageString $lngId)) - $((Get-GraphObjectName $obj $objectType))" } diff --git a/Extensions/EndpointManager.psm1 b/Extensions/EndpointManager.psm1 index ac7dd86..a2950dc 100644 --- a/Extensions/EndpointManager.psm1 +++ b/Extensions/EndpointManager.psm1 @@ -10,7 +10,7 @@ This module is for the Endpoint Manager/Intune View. It manages Export/Import/Co #> function Get-ModuleVersion { - '3.1.9' + '3.1.10' } function Invoke-InitializeModule @@ -106,9 +106,8 @@ function Invoke-InitializeModule Permissons=@("DeviceManagementConfiguration.ReadWrite.All") PropertiesToRemove = @("privacyAccessControls") PostFileImportCommand = { Start-PostFileImportDeviceConfiguration @args } - PreCopyCommand = { Start-PreCopyDeviceConfiguration @args } PostCopyCommand = { Start-PostCopyDeviceConfiguration @args } - PostExportCommand = { Start-PostExportDeviceConfiguration @args } + PostGetCommand = { Start-PostGetDeviceConfiguration @args } GroupId = "DeviceConfiguration" }) @@ -204,6 +203,7 @@ function Invoke-InitializeModule Permissons=@("Organization.ReadWrite.All") Icon = "Branding" SkipRemoveProperties = @('Id') + GroupId = "Azure" }) Add-ViewItem (New-Object PSObject -Property @{ @@ -510,6 +510,7 @@ function Invoke-InitializeModule Permissons=@("DeviceManagementRBAC.ReadWrite.All") PostExportCommand = { Start-PostExportScopeTags @args } ImportOrder = 10 + DocumentAll = $true GroupId = "TenantAdmin" }) @@ -954,17 +955,17 @@ function Start-PostCopyDeviceConfiguration } } -function Start-PreCopyDeviceConfiguration +function Start-PostGetDeviceConfiguration { - param($obj, $objectType, $newName) - - if(($obj.omaSettings | measure).Count -gt 0) + param($obj, $objectType) + + if(($obj.Object.omaSettings | measure).Count -gt 0) { - foreach($omaSetting in ($obj.omaSettings | Where isEncrypted -eq $true)) + foreach($omaSetting in ($obj.Object.omaSettings | Where isEncrypted -eq $true)) { if($omaSetting.isEncrypted -eq $false) { continue } - $xmlValue = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations/$($obj.Id)/getOmaSettingPlainTextValue(secretReferenceValueId='$($omaSetting.secretReferenceValueId)')" + $xmlValue = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations/$($obj.Object.Id)/getOmaSettingPlainTextValue(secretReferenceValueId='$($omaSetting.secretReferenceValueId)')" if($xmlValue.Value) { $omaSetting.isEncrypted = $false @@ -982,49 +983,7 @@ function Start-PreCopyDeviceConfiguration } } } - } - - $false -} - -function Start-PostExportDeviceConfiguration -{ - param($obj, $objectType, $path) - - $fileName = "$path\$((Remove-InvalidFileNameChars (Get-GraphObjectName $obj $objectType))).json" - - if(($obj.omaSettings | measure).Count -gt 0) - { - $updated = $false - foreach($omaSetting in @(($obj.omaSettings | Where isEncrypted -eq $true))) - { - if($omaSetting.isEncrypted -eq $false) { continue } - - # Get decrypted value and mark OMA-URI setting as not encrypted - $xmlValue = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations/$($obj.Id)/getOmaSettingPlainTextValue(secretReferenceValueId='$($omaSetting.secretReferenceValueId)')" - if($xmlValue.Value) - { - $omaSetting.isEncrypted = $false - $omaSetting.secretReferenceValueId = $null - if($omaSetting.'@odata.type' -eq "#microsoft.graph.omaSettingStringXml" -or - $omaSetting.'value@odata.type' -eq "#Binary") - { - $Bytes = [System.Text.Encoding]::UTF8.GetBytes($xmlValue.Value) - $omaSetting.value = [Convert]::ToBase64String($bytes) - } - else - { - $omaSetting.value = $xmlValue.Value - } - $updated = $true - } - } - - if($updated) - { - $obj | ConvertTo-Json -Depth 20 | Out-File -LiteralPath $fileName -Force - } - } + } } #endregion @@ -1251,6 +1210,22 @@ function Add-ScriptExtensions { $tmp.Children.Insert($index, $btnDownload) } + + $btnDownload = New-Object System.Windows.Controls.Button + $btnDownload.Content = 'Edit' + $btnDownload.Name = 'btnEdit' + $btnDownload.Margin = "0,0,5,0" + $btnDownload.Width = "100" + + $btnDownload.Add_Click({ + Invoke-EditScript + }) + + $tmp = $form.FindName($buttonPanel) + if($tmp) + { + $tmp.Children.Insert($index, $btnDownload) + } } function Add-ScriptExportExtensions @@ -1280,7 +1255,7 @@ function Start-PostExportScripts { Write-Log "Export script $($obj.FileName)" $fileName = [IO.Path]::Combine($exportPath, $obj.FileName) - [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($obj.scriptContent)) | Out-File -LiteralPath $fileName -Force + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($obj.scriptContent)) | Out-File -LiteralPath $fileName -Force } } @@ -1306,6 +1281,87 @@ function Invoke-DownloadScript } } +function Invoke-EditScript +{ + if(-not $global:dgObjects.SelectedItem.Object.id) { return } + + $obj = (Get-GraphObject $global:dgObjects.SelectedItem $global:curObjectType) + Write-Status "" + if(-not $obj.Object.scriptContent) { return } + $script:currentScriptObject = $obj + + $script:editForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\EditScriptDialog.xaml") + + if(-not $script:editForm) { return } + + Set-XamlProperty $script:editForm "txtEditScriptTitle" "Text" "Edit: $($obj.Object.displayName)" + + $scriptText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($obj.Object.scriptContent)) + Set-XamlProperty $script:editForm "txtScriptText" "Text" $scriptText + + $script:currentModal = $null + if($global:grdModal.Children.Count -gt 0) + { + $script:currentModal = $global:grdModal.Children[0] + } + + Add-XamlEvent $script:editForm "btnSaveScriptEdit" "add_click" ({ + $scriptText = Get-XamlProperty $script:editForm "txtScriptText" "Text" + $bytes = [System.Text.Encoding]::UTF8.GetBytes($scriptText) + $encodedText = [Convert]::ToBase64String($bytes) + + if($script:currentScriptObject.Object.scriptContent -ne $encodedText) + { + # Save script + if(([System.Windows.MessageBox]::Show("Are you sure you want to update the script?`n`nObject:`n$($script:currentScriptObject.displayName)", "Update script?", "YesNo", "Warning")) -eq "Yes") + { + Write-Status "Update $($script:currentScriptObject.displayName)" + $obj = $script:currentScriptObject.Object | ConvertTo-Json -Depth 20 | ConvertFrom-Json + $obj.scriptContent = $encodedText + Start-GraphPreImport $obj $script:currentScriptObject.ObjectType + foreach($prop in $script:currentScriptObject.ObjectType.PropertiesToRemoveForUpdate) + { + Remove-Property $obj $prop + } + Remove-Property $obj "Assignments" + Remove-Property $obj "isAssigned" + + $json = ConvertTo-Json $obj -Depth 15 + + $objectUpdated = (Invoke-GraphRequest -Url "$($script:currentScriptObject.ObjectType.API)/$($script:currentScriptObject.Object.Id)" -Content $json -HttpMethod "PATCH") + if(-not $objectUpdated) + { + Write-Log "Failed to update script" 3 + [System.Windows.MessageBox]::Show("Failed to save the script object. See log for more information","Update failed!", "OK", "Error") + } + Write-Status "" + } + } + + $global:grdModal.Children.Clear() + if($script:currentModal) + { + $global:grdModal.Children.Add($script:currentModal) + } + [System.Windows.Forms.Application]::DoEvents() + }) + + Add-XamlEvent $script:editForm "btnCancelScriptEdit" "add_click" ({ + $global:grdModal.Children.Clear() + if($script:currentModal) + { + $global:grdModal.Children.Add($script:currentModal) + } + [System.Windows.Forms.Application]::DoEvents() + }) + + $global:grdModal.Children.Clear() + $script:editForm.SetValue([System.Windows.Controls.Grid]::RowProperty,1) + $script:editForm.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1) + $global:grdModal.Children.Add($script:editForm) | Out-Null + [System.Windows.Forms.Application]::DoEvents() +} + #endregion #region Terms and Conditions diff --git a/Extensions/IntuneAssignments.psm1 b/Extensions/IntuneAssignments.psm1 new file mode 100644 index 0000000..cd23ec0 --- /dev/null +++ b/Extensions/IntuneAssignments.psm1 @@ -0,0 +1,249 @@ +<# +.SYNOPSIS +Module for listing Intune assignments + +.DESCRIPTION + +.NOTES + Author: Mikael Karlsson +#> +function Get-ModuleVersion +{ + '1.0.0' +} + +function Invoke-InitializeModule +{ + Add-EMToolsViewItem (New-Object PSObject -Property @{ + Title = "Intune Assignments" + Id = "IntuneAssignments" + ViewID = "EMTools" + Permissons=@("DeviceManagementConfiguration.ReadWrite.All") + Icon="DeviceConfiguration" + ShowViewItem = { Show-EMToolsIntuneAssignments } + }) +} + +function Show-EMToolsIntuneAssignments +{ + if(-not $script:frmIntuneAssignments) + { + $script:frmIntuneAssignments = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsIntuneAssignments.xaml") #-AddVariables + + if(-not $script:frmIntuneAssignments) { return } + + Add-XamlEvent $script:frmIntuneAssignments "btnBrowseIntuneAssignmentsExportPath" "add_click" ({ + $folder = Get-Folder (Get-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text") "Select root folder for exported files" + if($folder) + { + Set-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text" $folder + } + }) + + Add-XamlEvent $script:frmIntuneAssignments "btnGetIntuneAssignments" "add_click" ({ + $folder = Get-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text" + if($folder) + { + Write-Status "Get Intune Assignments" + Get-EMIntuneAssignments $folder + Write-Status "" + } + }) + + Add-XamlEvent $script:frmIntuneAssignments "btnIntuneAssignmentsCopy" "add_click" ({ + $script:objAssignments | Select Name, Type, IncludedString, ExcludedString | ConvertTo-Csv -NoTypeInformation | Set-Clipboard + }) + + Add-XamlEvent $script:frmIntuneAssignments "btnIntuneAssignmentsSave" "add_click" ({ + + $dlgSave = New-Object -Typename System.Windows.Forms.SaveFileDialog + #$dlgSave.InitialDirectory = Get-SettingValue "IntuneRootFolder" $env:Temp + $dlgSave.FileName = $obj.FileName + $sf.DefaultExt = "*.csv" + $sf.Filter = "CSV (*.csv)|*.csv|All files (*.*)| *.*" + if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename) + { + $script:objAssignments | Select Name, Type, IncludedString, ExcludedString | ConvertTo-Csv -NoTypeInformation | Out-File -LiteralPath $dlgSave.Filename -Encoding UTF8 -Force + } + }) + } + + $global:grdToolsMain.Children.Clear() + $global:grdToolsMain.Children.Add($frmIntuneAssignments) +} + +function Get-EMIntuneAssignmentInfo +{ + param($rootDir) + + Write-Status "Gather Export Information" + + $path = "$rootDir\Groups" + + $script:htGroups = @{} + + foreach($file in (Get-Item -path "$path\*.json")) + { + $graphObj = (ConvertFrom-Json (Get-Content -LiteralPath $file.FullName -Raw)) + $htGroups.Add($graphObj.Id, $graphObj) + } + + $script:fileArr = @() + + foreach($path in [IO.Directory]::EnumerateDirectories($rootDir)) + { + if($path -eq "$rootDir\Groups") { continue } + + foreach($file in (Get-Item -path "$path\*.json" -Exclude @("*_settings.json","*_assignments.json"))) + { + $graphObj = (ConvertFrom-Json (Get-Content -LiteralPath $file.FullName -Raw)) + + $obj = New-Object PSObject -Property @{ + FileName = $file.Name + FileInfo = $file + Selected = $SelectedStatus + Object = $graphObj + } + + $script:fileArr += $obj + } + } +} + +function Get-EMIntuneAssignments +{ + param($folder) + + Set-XamlProperty $script:frmIntuneAssignments "dgIntuneAssignments" "ItemsSource" $null + + $folderDI = [IO.DirectoryInfo]$folder + if(-not $folderDI.Exists) { return } + + Get-EMIntuneAssignmentInfo $folder + + Write-Status "Collect exported assignments" + + $script:objAssignments = @() + + foreach($fileObj in $script:fileArr) + { + $obj = New-Object PSObject -Property @{ + Object = $fileObj.Object + Name = $fileObj.Object.DisplayName + Type = $null + Included = $null + Excluded = $null + IncludedString = "" + ExcludedString = "" + } + $obj.Included = @() + $obj.Excluded = @() + if($fileObj.Object.'@OData.Type') + { + $obj.Type = $fileObj.Object.'@OData.Type'.Split('.')[-1] + } + else + { + $obj.Type = $file.Directory.Parent.Name + } + + foreach($assignment in $fileObj.Object.assignments) + { + $assignmentObj = $null + $included = $true + + if($assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget" -or + $assignment.target.'@odata.type' -eq "#microsoft.graph.exclusionGroupAssignmentTarget") + { + if($script:htGroups.ContainsKey($assignment.target.groupId)) + { + $assignmentObj = $script:htGroups[$assignment.target.groupId].displayName + } + else + { + Write-Warning "Could not find a group with ID $($assignment.target.groupId)" + } + $included = $assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget" + } + elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allDevicesAssignmentTarget") + { + $assignmentObj = "All Devices" + } + elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allLicensedUsersAssignmentTarget") + { + $assignmentObj = "All Users" + } + + if($included) + { + $obj.Included += $assignmentObj + } + else + { + $obj.Excluded += $assignmentObj + } + } + $obj.IncludedString = $obj.Included -join ";" + $obj.ExcludedString = $obj.Excluded -join ";" + + $script:objAssignments += $obj + } + + Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_LostFocus" ({ + Invoke-IntueAssignmentFilterBoxChanged $this + }) + + Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_GotFocus" ({ + if($this.Tag -eq "1" -and $this.Text -eq "Filter") { $this.Text = "" } + Invoke-IntueAssignmentFilterBoxChanged $this ($script:frmIntuneAssignments.FindName("dgIntuneAssignments")) + }) + + Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_TextChanged" ({ + Invoke-IntueAssignmentFilterBoxChanged $this ($script:frmIntuneAssignments.FindName("dgIntuneAssignments")) + }) + + Invoke-IntueAssignmentFilterBoxChanged ($script:frmIntuneAssignments.FindName("txtIntuneAssignmentsFilter")) ($script:frmIntuneAssignments.FindName("dgIntuneAssignments")) + + $ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:objAssignments)) + + Set-XamlProperty $script:frmIntuneAssignments "dgIntuneAssignments" "ItemsSource" ([System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList)) +} + +function Invoke-IntueAssignmentFilterBoxChanged +{ + 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" + } + else + { + if($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false) { return } + $txtBox.FontStyle = "Normal" + $txtBox.Tag = $null + $txtBox.Foreground="Black" + $txtBox.Background="White" + + if($txtBox.Text) + { + $filter = { + param ($item) + + return ( $item.Name -match [regex]::Escape($txtBox.Text) -or $item.IncludedString -match [regex]::Escape($txtBox.Text) -or $item.ExcludedString -match [regex]::Escape($txtBox.Text) ) + } + } + } + + if($dgObject.ItemsSource -is [System.Windows.Data.ListCollectionView]) + { + # This causes odd behaviour with focus e.g. and item has to be clicked twice to be selected + $dgObject.ItemsSource.Filter = $filter + $dgObject.ItemsSource.Refresh() + } +} diff --git a/Extensions/IntuneTools.psm1 b/Extensions/IntuneTools.psm1 index 11c5712..cb7ece2 100644 --- a/Extensions/IntuneTools.psm1 +++ b/Extensions/IntuneTools.psm1 @@ -17,53 +17,19 @@ This module is for the Intune Tools View. .NOTES Author: Mikael Karlsson #> + +$global:EMToolsViewObject = $null + function Get-ModuleVersion { - '1.0.0' + '1.0.1' } function Invoke-InitializeModule { - $viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerTools.xaml") -AddVariables - - if(-not $viewPanel) { return } + Add-ADMXRegClasses - Add-ADMXRegClasses - - #Add menu group and items - $global:EMToolsViewObject = (New-Object PSObject -Property @{ - Title = "Intune Tools" - Description = "Additional tools for managing Intune" - ID = "EMTools" - ViewPanel = $viewPanel - ItemChanged = { Show-EMTool } - Activating = { Invoke-EMToolsActivatingView } - Authentication = (Get-MSALAuthenticationObject) - Authenticate = { Invoke-EMToolsAuthenticateToMSAL } - AppInfo = (Get-GraphAppInfo "EM" "d1ddf0e4-d672-4dae-b554-9d5bdfd93547") - SaveSettings = { Invoke-EMSaveSettings } - Permissions = @() - }) - - Add-ViewObject $global:EMToolsViewObject - - Add-ViewItem (New-Object PSObject -Property @{ - Title = "ADMX Import" - Id = "ADMXImport" - ViewID = "EMTools" - Permissons=@("DeviceManagementConfiguration.ReadWrite.All") - Icon="DeviceConfiguration" - ShowViewItem = { Show-ADMXIngestion } - }) - - Add-ViewItem (New-Object PSObject -Property @{ - Title = "Reg Values" - Id = "ADMXRegValues" - ViewID = "EMTools" - Permissons=@("DeviceManagementConfiguration.ReadWrite.All") - Icon="DeviceConfiguration" - ShowViewItem = { Show-ADMXRegValues } - }) + Add-EMToolsViewItem # https://docs.microsoft.com/en-us/windows/client-management/mdm/win32-and-centennial-app-policy-configuration # ADMX ingestion cannot write to these paths: @@ -95,6 +61,58 @@ function Invoke-InitializeModule "@ } +function Add-EMToolsViewItem +{ + param($viewItem) + + if(-not $global:EMToolsViewObject) + { + $viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerTools.xaml") -AddVariables + + if(-not $viewPanel) { return } + + #Add menu group and items + $global:EMToolsViewObject = (New-Object PSObject -Property @{ + Title = "Intune Tools" + Description = "Additional tools for managing Intune" + ID = "EMTools" + ViewPanel = $viewPanel + ItemChanged = { Show-EMTool } + Activating = { Invoke-EMToolsActivatingView } + Authentication = (Get-MSALAuthenticationObject) + Authenticate = { Invoke-EMToolsAuthenticateToMSAL } + AppInfo = (Get-GraphAppInfo "EM" "d1ddf0e4-d672-4dae-b554-9d5bdfd93547") + SaveSettings = { Invoke-EMSaveSettings } + Permissions = @() + }) + + Add-ViewObject $global:EMToolsViewObject + + Add-ViewItem (New-Object PSObject -Property @{ + Title = "ADMX Import" + Id = "ADMXImport" + ViewID = "EMTools" + Permissons=@("DeviceManagementConfiguration.ReadWrite.All") + Icon="DeviceConfiguration" + ShowViewItem = { Show-ADMXIngestion } + }) + + Add-ViewItem (New-Object PSObject -Property @{ + Title = "Reg Values" + Id = "ADMXRegValues" + ViewID = "EMTools" + Permissons=@("DeviceManagementConfiguration.ReadWrite.All") + Icon="DeviceConfiguration" + ShowViewItem = { Show-ADMXRegValues } + }) + } + + if($viewItem) + { + Add-ViewItem $viewItem + } +} + function Invoke-EMToolsActivatingView { diff --git a/README.md b/README.md index d504119..e605c73 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ The script can import the exported json files in multiple ways. * **Skip if object exists:** The script will look if there is an existing object with the same name and type. It will not import the file if existing object is detected -* **Replace (Preview):** If a existing object is detected, the script will +* **Replace (Preview):** If an existing object is detected, the script will * Import the file without assignments * Copy assignments from the existing object @@ -52,7 +52,7 @@ The script can import the exported json files in multiple ways. * **Update (Preview):** This will update the existing object. - Update does not support all the properties that import does and object types behaves differently during update e.g. Settings for Endpoint Security objects will not be cleared. There is no API for removing settings only adding. If a settings does not in the import file, the existing setting will be set to Not Configured. Settings Catalog replaces the whole settings property during update. + The update APIs does not support all the properties that the import API sdoes and object types behaves differently during update e.g. Settings for **Endpoint Security** objects will not be cleared. There is no API for removing settings, only adding. If a setting does not exist in the import file, the existing setting will be set to *Not Configured*. **Settings Catalog** replaces the whole settings property during update. This has been tested with all supported object types *except* Import Scripts (Shell), Android OEM Config and Apple Enrollment Types. @@ -115,6 +115,11 @@ Additional Intune Tools is included in the script. * List (Key/Value pair) * This tool creates a custom ADMX file based on the specified registry keys. +* Intune Assignments + * Quickly gather all assignments from an export + * Easily identify all profiles/polices a group is assigned to + * Export as CSV to analyse in Excel + See [ADMX Import](ADMXImport.md) for more information about the ADMX tools ## Change log @@ -187,16 +192,18 @@ Android Store Apps are **not** imported. The Create API is documented in Microso Using multiple tenants support causes multiple logins/consent prompts the first time if 'Microsoft Graph PowerShell' is used. Querying the API for tenant list uses a different scope that is not included by default in the 'Microsoft Graph PowerShell' app. -Using multiple tenants support *might* cause and endless loop in the login screen and cause duplicate accounts in token cache. Actual cause is not found yet but it happens on rare occasions and it looks like it happens when a guest account is used. Workaround: Cancel the login, restart the script, logout and restart the script again. +~~Using multiple tenants support *might* cause and endless loop in the login screen and cause duplicate accounts in token cache. Actual cause is not found yet but it happens on rare occasions and it looks like it happens when a guest account is used. Workaround: Cancel the login, restart the script, logout and restart the script again.~~ - Not seen this in a long time. Please create issue if this happens When multi tenant settings is Enabled/Disabled, the Profile Info is not updated until the account is changed or app is restarted. Profile Info popup is built after logon. -The list applications API might not list an imported app immediately after the import. Click Refresh to reload the application objects. +The *List Applications* API might not list an imported app immediately after the import. Click *Refresh* to reload the application objects. When using the filter box to search for items, the checkbox must be clicked twice to select an item. Logout will only clear the token from cache and not from the browser e.g. if login is triggered after a logout, the user will still be listed in the 'Select user' dialog. +Referenced settings will NOT be imported/copied. There is no value stored in a property on the object for these settings. Example: A VPN profile has certificates as referenced properties. The certificates must be added manually after import/copy. + See [Documentation](Documentation.md) for issues regarding the documentation process. ## TIP diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 3f29b46..ee3aa73 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,41 @@ # Release Notes +## 3.2.1 - 2021-09-04 + +**New features** + +- PowerShell Scripts can now be viewed and edited in the tool +- Intune Tools + - Added Intune Assignment - Simple tool to quickly gather all assignments from exported objects +- Documentation + - Added documentation support for + - Scope (Tags) + Note: This will generate one section for all Scopes in the word document + - Health Scripts (Remediation Scripts) + +**Fixes** + +* General + + * Custom Device Configuration profiles will convert encrypted OMA URI values when the full object is loaded instead during Copy and Export. + * All file exports are now saved in UTF8 + +* Compare + + * Fixed issue where the wrong name was specified if the compare object was missing + * Administrative Templates, Settings Catalog and Endpoint Security will always compare based on documentation. + * Encrypted OMA URI values are now supported + +* Documentation + + * Minor updates to support documenting all objects of a specific object type in one section instead of one section per object + * Fixed "Not Configured" value issues for empty arrays + * Fixed documentation of Microsoft 365 Apps when XML is used + * Minor updates on VPN profile documentation. EAP XML will be in XML format and removed duplicate SplitTunneling values. + Note: The EAP XML will require manual update of the column sizes in Word + + + ## 3.2.0 - 2021-08-15 **New features** diff --git a/Xaml/EditScriptDialog.xaml b/Xaml/EditScriptDialog.xaml new file mode 100644 index 0000000..8d8055a --- /dev/null +++ b/Xaml/EditScriptDialog.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xaml/EndpointManagerToolsIntuneAssignments.xaml b/Xaml/EndpointManagerToolsIntuneAssignments.xaml new file mode 100644 index 0000000..058b8ef --- /dev/null +++ b/Xaml/EndpointManagerToolsIntuneAssignments.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +