Files
macOS_IntuneManagement/Extensions/Documentation.psm1
Mikael Karlsson 46435b5717 3.4.0
New features and fixes
2022-03-01 17:54:14 +11:00

4012 lines
142 KiB
PowerShell

<#
Documentation for Intune
This module contains the base documentation function for document objects in Intune
This module will document Settings objects (Settings Catalog, Endpoint Security and Administrative Templates) and
property objects like Configuration Profiles and Comliance Policies
Property objectes are documented based on the ObjectCategories.json file and the json files in the Documentation\ObjectInfo folder.
These json files contains the definition of each property of an object
Settings objects are documented with MS Graph APIs.
A basic Output provider is included that exports the objects to a CSV file
#>
$global:documentationOutput = @()
$global:documentationProviders = @()
function Get-ModuleVersion
{
'1.1.0'
}
function Invoke-InitializeModule
{
# Make sure we add the default Output types
Add-OutputType
$script:columnHeaders = @{
Name="Inputs.displayNameLabel"
Value="TableHeaders.value"
Description="TableHeaders.description"
GroupMode="SettingDetails.modeTableHeader" #assignmentTypeSelectionLabel?
Group="TableHeaders.assignedGroups"
Groups="TableHeaders.groups"
useDeviceContext="SettingDetails.installContextLabel"
uninstallOnDeviceRemoval="SettingDetails.UninstallOnRemoval"
isRemovable="SettingDetails.installAsRemovable"
vpnConfigurationId="PolicyType.vpn"
Action="SettingDetails.actionColumnName"
Schedule="ScheduledAction.List.schedule"
MessageTemplate="ScheduledAction.Notification.messageTemplate"
EmailCC="ScheduledAction.Notification.additionalRecipients"
Filter="AssignmentFilters.assignmentFilterColumnHeader"
Rule="ApplicabilityRules.GridLabel.Rule"
ValueWithLabel="TableHeaders.value"
Status="TableHeaders.status"
CombinedValueWithLabel="TableHeaders.value"
CombinedValue="TableHeaders.value"
useDeviceLicensing="TableHeaders.licenseType"
#filterMode="Filter mode" # Not in any string file yet
}
}
function Invoke-ShowMainWindow
{
$button = [System.Windows.Controls.Button]::new()
$button.Content = "Document"
$button.Name = "btnDocument"
$button.MinWidth = 100
$button.Margin = "0,0,5,0"
$button.IsEnabled = $false
$button.ToolTip = "Document selected objects"
$button.Add_Click({
$objects = ?? ($global:dgObjects.ItemsSource | Where IsSelected -eq $true) $global:dgObjects.SelectedItem
Show-DocumentationForm -Objects $objects
})
$global:spSubMenu.RegisterName($button.Name, $button)
$global:spSubMenu.Children.Insert(0, $button)
}
function Invoke-EMSelectedItemsChanged
{
$hasSelectedItems = ($global:dgObjects.ItemsSource | Where IsSelected -eq $true) -or ($null -ne $global:dgObjects.SelectedItem)
Set-XamlProperty $global:dgObjects.Parent "btnDocument" "IsEnabled" $hasSelectedItems
}
function Invoke-GraphObjectsChanged
{
$btnDocument = $global:spSubMenu.Children | Where-Object { $_.Name -eq "btnDocument" }
$btnExport = $global:spSubMenu.Children | Where-Object { $_.Name -eq "btnExport" }
if($btnDocument -and $btnExport)
{
$btnDocument.Visibility = $btnExport.Visibility
}
}
function Invoke-ViewActivated
{
if($global:currentViewObject.ViewInfo.ID -ne "IntuneGraphAPI") { return }
$tmp = $mnuMain.Items | Where Name -eq "EMBulk"
if($tmp)
{
$tmp.AddChild(([System.Windows.Controls.Separator]::new())) | Out-Null
$subItem = [System.Windows.Controls.MenuItem]::new()
$subItem.Header = "_Document Types"
$subItem.Add_Click({Invoke-DocumentObjectTypes})
$tmp.AddChild($subItem)
$subItem = [System.Windows.Controls.MenuItem]::new()
$subItem.Header = "D_ocument Selected"
$subItem.Add_Click({Invoke-DocumentSelectedObjects})
$tmp.AddChild($subItem)
}
}
function Set-DocColumnHeaderLanguageId
{
param($columnName, $lngId)
if(-not $script:columnHeaders -or -not $lngId) { return }
if($script:columnHeaders.ContainsKey($columnName))
{
$script:columnHeaders[$columnName] = $lngId
}
else
{
$script:columnHeaders.Add($columnName, $lngId)
}
}
function Invoke-DocTranslateColumnHeader
{
param($columnName)
$lngText = ""
if($script:columnHeaders.ContainsKey($columnName))
{
$lngText = Get-LanguageString $script:columnHeaders[$columnName]
}
(?? $lngText $columnName)
}
function Add-OutputType
{
param($outputInfo)
if(-not $global:documentationOutput)
{
$global:documentationOutput = @()
}
if($global:documentationOutput.Count -eq 0)
{
$global:documentationOutput += [PSCustomObject]@{
Name = "None (Raw output only)"
Value = "none"
}
$global:documentationOutput += [PSCustomObject]@{
Name="CSV"
Value="csv"
OutputOptions = (Add-CSVOptionsControl)
Activate = { Invoke-CSVActivate @args }
PreProcess = { Invoke-CSVPreProcessItems @args }
Process = { Invoke-CSVProcessItem @args }
}
}
if(!$outputInfo) { return }
$global:documentationOutput += $outputInfo
}
function Add-DocumentationProvicer
{
param($docProvider)
if(-not $global:documentationProviders)
{
$global:documentationProviders = @()
}
$global:documentationProviders += $docProvider
}
function Get-ObjectDocumentation
{
param($documentationObj)
$additionalInfo = ""
if($documentationObj.Object.'@ObjectFromFile' -eq $true)
{
$additionalInfo = " - From File"
}
Write-Status "Get documentation info for $((Get-GraphObjectName $documentationObj.Object $documentationObj.ObjectType)) ($($documentationObj.ObjectType.Title))$additionalInfo"
$status = $null
$inputType = "Settings"
if(-not $script:scopeTags)
{
$script:scopeTags = (Invoke-GraphRequest -Url "/deviceManagement/roleScopeTags").Value
}
$obj = $documentationObj.Object
$objectType = $documentationObj.ObjectType
$script:currentObject = $obj
$script:languageStrings = $null
$script:CurrentSubCategory = $null
$script:objectBasicInfo = @()
$script:objectSettingsData = @()
$script:objectComplianceActionData = @()
$script:applicabilityRules = @()
$script:objectAssignments = @()
$updateFilteredObject = $true
$type = $obj.'@OData.Type'
$defaultDocumentationProperties = @("Name","Value")
$retObj = $null
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.DocumentObject)
{
$retObj = & $docProvider.DocumentObject $documentationObj
if($retObj -ne $null)
{
break
}
}
}
$propertyObjectProperties = @("Name","Value","Category","SubCategory","RawValue","RawJsonValue","DefaultValue","UnconfiguredValue","EntityKey","Description","Enabled")
#region Custom documentation
if($retObj)
{
$status = $retObj.ErrorText
$properties = $retObj.Properties
$inputType = ?? $retObj.InputType "Property"
$defaultDocumentationProperties = ?? $retObj.DefaultDocumentationProperties $defaultDocumentationProperties
}
#endregion
#region Manually created file - ODataType
elseif([IO.File]::Exists(($global:AppRootFolder + "\Documentation\ObjectInfo\$($obj.'@OData.Type').json")))
{
$inputType = "Property"
# Process object based on OData type
Invoke-TranslateCustomProfileObject $obj "$($obj.'@OData.Type')" | Out-Null
$properties = $propertyObjectProperties
}
#endregion
#region Manually created file - ObjectType id
elseif($objectType -and [IO.File]::Exists(($global:AppRootFolder + "\Documentation\ObjectInfo\#$($objectType.Id).json")))
{
$inputType = "Property"
# Process object based on Intune Object Type ($objectType)
# '#' is added to front of name to distinguish manually created files from generated files
Invoke-TranslateCustomProfileObject $obj "#$($objectType.Id)" | Out-Null
$properties = $propertyObjectProperties
}
#endregion
#region Settings Catalog
elseif($type -eq "#microsoft.graph.deviceManagementConfigurationPolicy")
{
Invoke-TranslateSettingsObject $obj $objectType | Out-Null
$properties = @("Name","Value","RootCategory","Category","RawValue","RawJsonValue","DefaultValue","Description")
}
#endregion
#region Endpoint Security
elseif($type -eq "#microsoft.graph.deviceManagementIntent")
{
Invoke-TranslateIntentObject $obj $objectType | Out-Null
$properties = @("Name","Value","Category","RawValue","SettingId","Description")
}
#endregion
#region Administrative Templates
elseif($type -eq "#microsoft.graph.groupPolicyConfiguration")
{
Invoke-TranslateADMXObject $obj $objectType | Out-Null
$properties = @("Name","Status","Value","Category","CategoryPath","RawValue","ValueWithLabel","Created","Modified", "Class", "DefinitionId")
$defaultDocumentationProperties = @("Name","Status","Value")
$updateFilteredObject = $false
$inputType = "Property"
}
#endregion
#region Profile Types e.g. DeviceConfiguration Policies etc
elseif($type)
{
$inputType = "Property"
$processed = $true
$processed = Invoke-TranslateProfileObject $obj
if($processed -eq $false)
{
$errText = "No object file or object info found for $((Get-GraphObjectName $obj $objType)) ($($obj.'@OData.Type'))`n`nObject type not supported for documentation"
# Object not processed
$status = $errText
Write-Log $errText 3
}
$properties = $propertyObjectProperties
}
#endregion
if($script:objectBasicInfo.Count -gt 0)
{
Add-ScopeTagStrings $obj
}
if($obj.scheduledActionsForRule.scheduledActionConfigurations)
{
Invoke-TranslateScheduledActionType $obj.scheduledActionsForRule
}
if(($obj.assignments | measure).Count -gt 0)
{
Invoke-TranslateAssignments $obj
}
$script:settingsProperties = $properties
[PSCustomObject]@{
BasicInfo = $script:objectBasicInfo
Settings = $script:objectSettingsData
ComplianceActions = $script:objectComplianceActionData
ApplicabilityRules = $script:applicabilityRules
Assignments = $script:objectAssignments
DisplayProperties = $properties
DefaultDocumentationProperties = $defaultDocumentationProperties
ErrorText = $status
InputType = $inputType
UpdateFilteredObject = $updateFilteredObject
UnconfiguredProperties = $obj."@UnconfiguredProperties"
}
}
function Get-DocumentedSettings
{
$script:objectSettingsData
}
function Invoke-ObjectDocumentation
{
param($documentationObj)
$global:intentCategories = $null
$global:catRecommendedSettings = $null
$global:intentCategoryDefs = $null
$global:cfgCategories = $null
$script:DocumentationLanguage = "en"
$script:objectSeparator = [System.Environment]::NewLine
$script:propertySeparator = ","
Get-ObjectDocumentation $documentationObj
}
function Add-RawDataInfo
{
param($obj, $objectType)
$params = @{}
if($txtDocumentationRawData.Text)
{
$txtDocumentationRawData.Text += "`n`n"
}
$txtDocumentationRawData.Text += "#########################################################"
$txtDocumentationRawData.Text += "`n`n# Object: $((Get-GraphObjectName $obj $objectType))"
$txtDocumentationRawData.Text += "`n# Type: $($obj.'@OData.Type')"
$txtDocumentationRawData.Text += "`n`n#########################################################"
if($script:objectBasicInfo.Count -gt 0)
{
$txtDocumentationRawData.Text += "`n`n# Basic Info`n`n"
$txtDocumentationRawData.Text += (($script:objectBasicInfo | Select -Property Name,Value | ConvertTo-Csv -NoTypeInformation @params) -join ([System.Environment]::NewLine))
}
if($script:objectSettingsData)
{
$txtDocumentationRawData.Text += "`n`n# Object Settings`n`n"
$txtDocumentationRawData.Text += (($script:objectSettingsData | Select -Property $script:settingsProperties | ConvertTo-Csv -NoTypeInformation @params) -join ([System.Environment]::NewLine))
}
if(($documentedObj.ApplicabilityRules | measure).Count -gt 0)
{
$txtDocumentationRawData.Text += "`n`n# Applicability rules`n`n"
$txtDocumentationRawData.Text += (($script:applicabilityRules | Select Rule,Property,Value,Category | ConvertTo-Csv -NoTypeInformation) -join ([System.Environment]::NewLine))
}
if($script:objectComplianceActionData.Count -gt 0)
{
$txtDocumentationRawData.Text += "`n`n# Compliance actions`n`n"
$txtDocumentationRawData.Text += (($script:objectComplianceActionData | Select Action,Schedule,MessageTemplate,EmailCC,Category | ConvertTo-Csv -NoTypeInformation) -join ([System.Environment]::NewLine))
}
if($script:objectAssignments.Count -gt 0)
{
if($script:objectAssignments.Count -gt 0 -and $script:objectAssignments[0].RawIntent)
{
$properties = @("GroupMode","Group","Filter","FilterMode","Category","SubCategory","RawIntent")
}
else
{
$properties = @("GroupMode","Group","Filter","FilterMode","Category")
}
$txtDocumentationRawData.Text += "`n`n# Assignments`n`n"
$txtDocumentationRawData.Text += (($script:objectAssignments | Select -Property $properties | ConvertTo-Csv -NoTypeInformation) -join ([System.Environment]::NewLine))
}
}
function Get-ObjectTypeString
{
param($obj, $objectType)
$objTypeId = ?? $objectType.GroupId $objectType
if($objTypeId -eq "DeviceConfiguration")
{
return (Get-LanguageString "SettingDetails.deviceConfigurationTitle")
}
elseif($objTypeId -eq "CompliancePolicies")
{
return (Get-LanguageString "SettingDetails.deviceComplianceTitle")
}
elseif($objTypeId -eq "Apps")
{
return (Get-LanguageString "SettingDetails.clientAppsTitle")
}
elseif($objTypeId -eq "WinEnrollment")
{
return (Get-LanguageString "SettingDetails.windowsEnrollmentTitle")
}
elseif($objTypeId -eq "AppleEnrollment")
{
return (Get-LanguageString "SettingDetails.appleEnrollmentTitle")
}
elseif($objTypeId -eq "PolicySets")
{
return (Get-LanguageString "SettingDetails.policySetsTitle")
}
elseif($objTypeId -eq "AppConfiguration")
{
return (Get-LanguageString "SettingDetails.appConfiguration")
}
elseif($objTypeId -eq "AppProtection")
{
return (Get-LanguageString "SettingDetails.appProtectionPolicy")
}
elseif($objTypeId -eq "CustomAttributes")
{
return (Get-LanguageString "Titles.customAttributes")
}
elseif($objTypeId -eq "ConditionalAccess")
{
return (Get-LanguageString "SecurityTemplate.conditionalAccess")
}
elseif($objTypeId -eq "EndpointAnalytics")
{
return (Get-LanguageString "SettingDetails.healthMonScopeBootPerf")
}
elseif($objTypeId -eq "EndpointSecurity")
{
return (Get-LanguageString "PolicyType.EndpointSecurityTemplate.default")
}
elseif($objTypeId -eq "EnrollmentRestrictions")
{
return (Get-LanguageString "Titles.enrollmentRestrictions")
}
elseif($objTypeId -eq "Scripts")
{
return (Get-LanguageString "Titles.scriptManagement")
}
elseif($objTypeId -eq "WinUpdatePolicies")
{
return (Get-LanguageString "Titles.windows10UpdateRings")
}
elseif($objTypeId -eq "WinFeatureUpdates")
{
return (Get-LanguageString "Titles.featureUpdateDeployments")
}
elseif($objTypeId -eq "WinQualityUpdates")
{
return (Get-LanguageString "Titles.windows10QualityUpdate")
}
elseif($objTypeId -eq "TenantAdmin")
{
return (Get-LanguageString "Titles.tenantAdmin")
}
elseif($objTypeId -eq "Azure")
{
return "Azure"
}
}
function Add-BasicDefaultValues
{
param($obj, $objectType)
Add-BasicPropertyValue (Get-LanguageString "SettingDetails.nameName") (Get-GraphObjectName $obj $objectType)
Add-BasicPropertyValue (Get-LanguageString "SettingDetails.descriptionName") $obj.description
$objInfo = Get-TranslationFiles $obj.'@OData.Type'
$appType = Get-GraphAppType $obj
if($objInfo)
{
$platformType = Get-LanguageString "Platform.$($objInfo.PlatformLanguageId)"
$profileType = Get-LanguageString "ConfigurationTypes.$($objInfo.PolicyType)"
if($platformType) { Add-BasicPropertyValue (Get-LanguageString "SettingDetails.platformSupported") $platformType }
if($profileType) { Add-BasicPropertyValue (Get-LanguageString "TableHeaders.configurationType") $profileType }
}
if($appType)
{
$appTypeName = Get-LanguageString "AppType.$($appType.LanguageId)"
if($appTypeName) { Add-BasicPropertyValue (Get-LanguageString "Inputs.installationSourceLabel") $appTypeName }
}
if($obj."@OData.Type" -eq "#microsoft.graph.deviceManagementIntent")
{
if(-not $script:baseLineTemplates)
{
$script:baseLineTemplates = (Invoke-GraphRequest -Url "/deviceManagement/templates").Value
}
$baseLineTemplate = $script:baseLineTemplates | Where Id -eq $obj.templateId
if(-not $baseLineTemplate)
{
Write-Log "Could not find Baseline Template with Id $($obj.templateId)" 3
}
$platformType = Get-LanguageString "Platform.$($baseLineTemplate.platformType)"
if($platformType) { Add-BasicPropertyValue (Get-LanguageString "SettingDetails.platformSupported") $platformType }
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.Category") (Get-IntentCategory (?: ($baseLineTemplate.templateSubtype -eq "none") $baseLineTemplate.templateType $baseLineTemplate.templateSubtype))
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.policyType") $baseLineTemplate.displayName
}
if($obj.deviceManagementApplicabilityRuleDeviceMode)
{
# Future setting?
}
if($obj.deviceManagementApplicabilityRuleOsEdition)
{
$arrOSList = @()
foreach($os in $obj.deviceManagementApplicabilityRuleOsEdition.osEditionTypes)
{
$arrOSList += Get-LanguageString "ApplicabilityRules.$os"
}
$script:applicabilityRules += [PSCustomObject]@{
Rule = Get-LanguageString "ApplicabilityRules.$((?: ($obj.deviceManagementApplicabilityRuleOsEdition.ruleType -eq "exclude") "dontAssignIf" "assignIf"))"
Property = Get-LanguageString "ApplicabilityRules.windows10OsEdition"
Value = $arrOSList -join $script:objectSeparator
Id = "deviceManagementApplicabilityRuleOsEdition"
Category = Get-LanguageString "SettingDetails.applicabilityRules"
}
}
if($obj.deviceManagementApplicabilityRuleOsVersion)
{
$script:applicabilityRules += [PSCustomObject]@{
Rule = Get-LanguageString "ApplicabilityRules.$((?: ($obj.deviceManagementApplicabilityRuleOsVersion.ruleType -eq "exclude") "dontAssignIf" "assignIf"))"
Property = Get-LanguageString "ApplicabilityRules.windows10OsVersion"
Value = "$($obj.deviceManagementApplicabilityRuleOsVersion.minOSVersion) $((Get-LanguageString "ApplicabilityRules.toText")) $($obj.deviceManagementApplicabilityRuleOsVersion.maxOSVersion)"
Id = "deviceManagementApplicabilityRuleOsVersion"
Category = Get-LanguageString "SettingDetails.applicabilityRules"
}
}
}
function Add-BasicAdditionalValues
{
param($obj, $objectType)
if($obj.createdDateTime)
{
$tmpDate = ([DateTime]::Parse($obj.createdDateTime))
Add-BasicPropertyValue (Get-LanguageString "Inputs.createdDateTime") "$($tmpDate.ToLongDateString()) $($tmpDate.ToLongTimeString())"
}
if($obj.lastModifiedDateTime)
{
$tmpDate = ([DateTime]::Parse($obj.lastModifiedDateTime))
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.lastModified") "$($tmpDate.ToLongDateString()) $($tmpDate.ToLongTimeString())"
}
elseif($obj.modifiedDateTime)
{
$tmpDate = ([DateTime]::Parse($obj.modifiedDateTime))
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.lastModified") "$($tmpDate.ToLongDateString()) $($tmpDate.ToLongTimeString())"
}
if($obj.version)
{
Add-BasicPropertyValue (Get-LanguageString "SettingDetails.eDPPolicyAppsListVersionName") $obj.version
}
#Add-BasicPropertyValue (Get-LanguageString "TableHeaders.type") $obj.'@OData.Type'
}
function Add-BasicPropertyValue
{
param($name, $value)
$script:objectBasicInfo += [PSCustomObject]@{
Name=$name
Value=$value
Category=(Get-LanguageString "SettingDetails.basics")
}
}
function Add-ScopeTagStrings
{
param($obj)
$objScopeTags = @()
if(($obj.roleScopeTagIds | measure).Count -gt 0)
{
foreach($scopeTagId in $obj.roleScopeTagIds)
{
$scopeTagName = $scopeTagId
if($scopeTagId -eq "0")
{
$scopeTagName = (Get-LanguageString "SettingDetails.default")
}
elseif($script:scopeTags)
{
$scopeTagObj = $script:scopeTags | Where Id -eq $scopeTagId
if($scopeTagObj.displayName)
{
$scopeTagName = $scopeTagObj.displayName
}
}
$objScopeTags += $scopeTagName
}
}
if($objScopeTags.Count -gt 0)
{
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.scopeTags") ($objScopeTags -join $script:objectSeparator)
}
}
function Get-ObjectPlatformFromType
{
param($obj)
$platform = $null
$lowerAppType = $obj.'@OData.Type'.ToLower()
if($lowerAppType.Contains("ios"))
{
$platform = "iOS"
}
elseif($lowerAppType.Contains("mac"))
{
$platform = "Mac"
}
elseif($lowerAppType.Contains("windowsphone"))
{
$platform = "WindowsPhone"
}
elseif($lowerAppType.Contains("windows") -or $lowerAppType.Contains("win32") -or $lowerAppType.Contains("mirosoftstore"))
{
$platform = "Windows10"
}
elseif($lowerAppType.Contains("android"))
{
$platform = "Android"
}
$platform
}
#region Admin Templates
function Invoke-TranslateADMXObject
{
param($obj, $objectType)
Add-BasicDefaultValues $obj $objectType
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.configurationType") (Get-LanguageString "Titles.groupPolicy")
#Add-BasicPropertyValue (Get-LanguageString "SettingDetails.platformSupported") $platformType
Add-BasicAdditionalValues $obj $objectType
$params = @{}
## Set language
if($script:DocumentationLanguage)
{
$params.Add("AdditionalHeaders", @{"Accept-Language"=$script:DocumentationLanguage})
}
if(-not $script:admxCategories)
{
$script:admxCategories = (Invoke-GraphRequest "deviceManagement/groupPolicyCategories?`$expand=parent(`$select=id, displayName, isRoot),definitions(`$select=id, displayName, categoryPath, classType, policyType)&`$select=id, displayName, isRoot" -ODataMetadata "skip" @params).value
}
if(!$obj.definitionValues)
{
$definitionValues = (Invoke-GraphRequest "deviceManagement/groupPolicyConfigurations('$($obj.Id)')/definitionValues?`$expand=definition(`$select=id,classType,displayName,policyType,groupPolicyCategoryId)" -ODataMetadata "minimal").value
}
else
{
# Documenting exported json
$definitionValues = $obj.definitionValues
}
$enabledStr = Get-LanguageString "Inputs.enabled"
$disabledStr = Get-LanguageString "Inputs.disabled"
foreach($definitionValue in $definitionValues)
{
if(-not $definitionValue.definition -and $definitionValues.'definition@odata.bind')
{
$definition = Invoke-GraphRequest -Url $definitionValue.'definition@odata.bind' -ODataMetadata "minimal" @params
if($definition)
{
$definitionValue | Add-Member -MemberType NoteProperty -Name "definition" -Value $definition
}
}
$categoryObj = $script:admxCategories | Where { $definitionValue.definition.id -in ($_.definitions.id) }
$category = $script:admxCategories.definitions | Where { $definitionValue.definition.id -in ($_.id) }
# Get presentation values for the current settings (with presentation object included)
if($definitionValue.presentationValues -or $obj.'@ObjectFromFile' -eq $true) #$definitionValue.'definition@odata.bind')
{
# Documenting exported json
#$presentationValues = (Invoke-GraphRequest -Url "$($definitionValue.'definition@odata.bind')/presentations?`$expand=presentation" -ODataMetadata "minimal").value
$presentationValues = $definitionValue.presentationValues
}
elseif($definitionValue.id)
{
$presentationValues = (Invoke-GraphRequest -Url "/deviceManagement/groupPolicyConfigurations/$($obj.id)/definitionValues/$($definitionValue.id)/presentationValues?`$expand=presentation" -ODataMetadata "minimal" @params).value
}
else
{
$presentationValues = $null
}
$value = $null
$rawValues = @()
$values = @()
$valuesWithLabel = @()
foreach($presentationValue in $presentationValues)
{
if(-not $presentationValue.presentation -and $presentationValue.'presentation@odata.bind')
{
$presentation = Invoke-GraphRequest -Url $presentationValue.'presentation@odata.bind'
if($presentation)
{
$presentationValue | Add-Member -MemberType NoteProperty -Name "presentation" -Value $presentation
}
}
$rawValue = $presentationValue.value
$label = $presentationValue.presentation.label
$value = $null
if($presentationValue.presentation.'@odata.type' -eq '#microsoft.graph.groupPolicyPresentationDropdownList')
{
$value = ($presentationValue.presentation.items | Where value -eq $rawValue).displayName
}
elseif($presentationValue.'@odata.type' -eq '#microsoft.graph.groupPolicyPresentationValueList')
{
$arrValues = @()
foreach($tmpValue in $presentationValue.values)
{
$arrValues += ($tmpValue.name + $script:propertySeparator + $tmpValue.value)
}
$value = $arrValues -join $script:objectSeparator
}
elseif($presentationValue.'@odata.type' -eq '#microsoft.graph.groupPolicyPresentationValueMultiText')
{
$value = $presentationValue.values -join $script:objectSeparator
}
else
{
#groupPolicyPresentationValueBoolean
#groupPolicyPresentationValueDecimal
#groupPolicyPresentationValueLongDecimal
#groupPolicyPresentationValueText
$value = $rawValue
}
$valuesWithLabel += "$label $value"
$values += $value
$rawValues += $rawValue
}
$status = (?: ($definitionValue.enabled -eq $true) $enabledStr $disabledStr)
$script:objectSettingsData += New-Object PSObject -Property @{
Name = $definitionValue.definition.displayName
Description = $definitionValue.definition.explainText
Status = $status
Value = $values -join $script:objectSeparator
CombinedValue = ($status + $script:objectSeparator + ($values -join $script:objectSeparator))
ValueWithLabel = $valuesWithLabel -join $script:objectSeparator
CombinedValueWithLabel = ($status + $script:objectSeparator + ($valuesWithLabel -join $script:objectSeparator))
RawValue = $rawValues -join $script:propertySeparator
Class = $definitionValue.definition.classType
DefinitionId = $definitionValue.definition.id
Created = $definitionValue.createdDateTime
Modified = $definitionValue.lastModifiedDateTime
#Category = $categoryObj.displayName
Category = $category.categoryPath
CategoryPath = $category.categoryPath
EntityKey = $definitionValue.definition.id # Required for Compare
}
}
$script:objectSettingsData = $script:objectSettingsData | Sort -Property CategoryPath
}
#endregion
#region Settings Catalog
function Invoke-TranslateSettingsObject
{
param($obj, $objectType)
$platformType = Get-LanguageString "Platform.$($obj.platforms)"
Add-BasicDefaultValues $obj $objectType
Add-BasicPropertyValue (Get-LanguageString "TableHeaders.configurationType") (Get-LanguageString "ConfigurationTypes.settingsCatalog")
Add-BasicPropertyValue (Get-LanguageString "SettingDetails.platformSupported") $platformType
Add-BasicAdditionalValues $obj $objectType
$params = @{}
## Set language
if($script:DocumentationLanguage)
{
$params.Add("AdditionalHeaders", @{"Accept-Language"=$script:DocumentationLanguage})
}
$cfgSettings = (Invoke-GraphRequest "/deviceManagement/configurationPolicies('$($obj.Id)')/settings?`$expand=settingDefinitions&top=1000" -ODataMetadata "minimal" @params).Value
if(-not $global:cfgCategories)
{
$global:cfgCategories = (Invoke-GraphRequest "/deviceManagement/configurationCategories?`$filter=platforms has 'windows10' and technologies has 'mdm'" -ODataMetadata "minimal" @params).Value
}
$categories = @{}
foreach($cfgSetting in $cfgSettings)
{
$defObj = $cfgSetting.settingDefinitions | Where id -eq $cfgSetting.settingInstance.settingDefinitionId
if(-not $defObj -or $categories.ContainsKey($defObj.categoryId)) { continue }
$catObj = $global:cfgCategories | Where Id -eq $defObj.categoryId
$rootCatObj = $global:cfgCategories | Where Id -eq $catObj.rootCategoryId
$catSettings = Invoke-GraphRequest "/deviceManagement/configurationSettings?`$filter=categoryId eq '$($defObj.categoryId)' and applicability/platform has 'windows10' and applicability/technologies has 'mdm'" -ODataMetadata "minimal" @params
$categories.Add($defObj.categoryId, (New-Object PSObject -Property @{
Category=$catObj
Settings=$catSettings
RootCategory=$rootCatObj
}))
}
Add-SettingsSetting $obj $objectType $categories $cfgSettings
}
function Add-SettingsSetting
{
param($obj, $objectType, $categories, $cfgSettings, $settigsDefs = $null)
foreach($cfgSetting in $cfgSettings)
{
$children = $null
$skipAdd = $false
$cfgInstance = ?? $cfgSetting.settingInstance $cfgSetting
if($cfgSetting.settingDefinitions)
{
$settigsDefs = $cfgSetting.settingDefinitions
}
$defaultValue = $null
$rawValue=$null
$rawJsonValue = $null
$defObj = $settigsDefs | Where id -eq $cfgInstance.settingDefinitionId
$catObj = $categories[$defObj.categoryId]
if($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance')
{
$rawValue = $cfgInstance.choiceSettingValue.value
$itemValue = ($defObj.Options | Where itemId -eq $rawValue).displayName
if($defObj.defaultOptionId)
{
$defaultValue = ($defObj.Options | Where itemId -eq $defObj.defaultOptionId).displayName
}
$children = $cfgInstance.choiceSettingValue.children
}
elseif($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingCollectionInstance')
{
$itemValues = @()
$itemRawValues = @()
foreach($colObj in $cfgInstance.simpleSettingCollectionValue)
{
$tmpValue = $colObj.value
$itemValues += ($defObj.Options | Where itemId -eq $tmpValue).displayName
$itemRawValues += $tmpValue
}
$rawValue = $itemValues -join $script:propertySeparator
$itemValue = $itemRawValues -join $script:propertySeparator
if($defObj.defaultOptionId)
{
$defaultValue = ($defObj.Options | Where itemId -eq $defObj.defaultOptionId).displayName
}
}
elseif($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance')
{
$itemValue = $cfgInstance.simpleSettingValue.value
$rawValue = $itemValue
if($defObj.defaultValue.value)
{
$defaultValue = $defObj.defaultValue.value
}
}
elseif($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance')
{
$itemValues = @()
$rawValue = $cfgInstance.simpleSettingCollectionValue
foreach($colObj in $cfgInstance.simpleSettingCollectionValue)
{
$itemValues += $colObj.value
}
if($defObj.defaultValue.value)
{
$defaultValue = $defObj.defaultValue.value
}
$itemValue = $itemValues -join $script:propertySeparator
}
elseif($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationGroupSettingInstance')
{
# This will skip adding the group itself as enabled...
# It will only add information about the children
Add-SettingsSetting $obj $objectType $categories $cfgInstance.groupSettingValue.children $settigsDefs
continue
}
elseif($cfgInstance.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance')
{
# ToDo: Fix support for other child types
# This assumes that all children are deviceManagementConfigurationSimpleSettingInstance
$objItems = @()
foreach($childObj in $cfgInstance.groupSettingCollectionValue)
{
$objProps = @()
foreach($childId in $defObj.childIds)
{
$childSetting = $childObj.children | Where settingDefinitionId -eq $childId
if($childSetting)
{
$objProps += $childSetting.simpleSettingValue.value
}
}
$objItems += $objProps -join $script:propertySeparator
}
$itemValue = $objItems -join $script:objectSeparator
$rawValue = $itemValue
$rawJsonValue = $cfgInstance.groupSettingCollectionValue | ConvertTo-Json -Depth 20 -Compress
}
else
{
Write-Log "Unsupported setting type: $($cfgInstance.'@odata.type')"
}
if(!$rawJsonValue -and $rawValue)
{
$rawJsonValue = $rawValue | ConvertTo-Json -Depth 20 -Compress
}
$script:objectSettingsData += New-Object PSObject -Property @{
Name=$defObj.displayName
Description=$defObj.description
Category=$catObj.Category.displayName
CategoryDescription=$catObj.Category.description
Value=$itemValue
RawValue=$rawValue
RawJsonValue=$rawJsonValue
DefaultValue=$defaultValue
RootCategory=$catObj.RootCategory.displayName
CategoryObject=$catObj
SettingId=$cfgInstance.settingDefinitionId
}
if($children)
{
Add-SettingsSetting $obj $objectType $categories $children $settigsDefs
}
}
}
#endregion
#region Intent Objects (Endpoint Security)
function Get-IntentCategory
{
param($templateType)
if($templateType -eq "accountProtection")
{
return (Get-LanguageString "SecurityTemplate.accountProtection")
}
elseif($templateType -eq "antivirus")
{
return (Get-LanguageString "SecurityTemplate.antivirus")
}
elseif($templateType -eq "diskEncryption")
{
return (Get-LanguageString "SecurityTemplate.diskEncryption")
}
elseif($templateType -eq "???")
{
return (Get-LanguageString "SecurityTemplate.eDR")
}
elseif($templateType -eq "attackSurfaceReduction")
{
return (Get-LanguageString "SecurityTemplate.aSR")
}
elseif($templateType -eq "attackSurfaceReduction")
{
return (Get-LanguageString "SecurityTemplate.aSR")
}
elseif($templateType -eq "securityBaseline" -or
$templateType -eq "advancedThreatProtectionSecurityBaseline" -or
$templateType -eq "microsoftEdgeSecurityBaseline")
{
return (Get-LanguageString "Titles.securityBaselines")
}
else
{
Write-Log "Could not translate templateSubtype $templateType"
return $templateType
}
}
function Invoke-TranslateIntentObject
{
param($obj, $objectType)
$params = @{}
if($script:DocumentationLanguage)
{
$params.Add("AdditionalHeaders", @{"Accept-Language"=$script:DocumentationLanguage})
}
Add-BasicDefaultValues $obj $objectType
Add-BasicAdditionalValues $obj $objectType
if($global:intentCategories -is [HashTable])
{
$categories = $global:intentCategories[$obj.TemplateId]
}
else
{
$global:intentCategories = @{}
$global:catRecommendedSettings = @{}
}
if($global:intentCategoryDefs -isnot [HashTable])
{
$global:intentCategoryDefs = @{}
}
if(-not $categories)
{
$categories = (Invoke-GraphRequest "/deviceManagement/templates/$($obj.TemplateId)/categories?`$expand=settingDefinitions" -ODataMetadata "minimal" @params).Value
$global:intentCategories.Add($obj.TemplateId, $categories)
}
$script:objectSettings = @()
foreach($category in ($categories | Sort -Property displayName))
{
# Get settings for the category. This will put them in the correct order...
if($obj.'@ObjectFromFile' -ne $true)
{
$settings = (Invoke-GraphRequest "/deviceManagement/intents/$($obj.Id)/categories/$($category.Id)/settings?`$expand=Microsoft.Graph.DeviceManagementComplexSettingInstance/Value" -ODataMetadata "minimal" @params).Value
}
else
{
$settings = $obj.settings
}
if($global:catRecommendedSettings.ContainsKey($category.Id))
{
$catRecommendedSettingsObj = $global:catRecommendedSettings[$obj.TemplateId]
}
else
{
# Get setting defenitions for the category
#$catDefObjs = (Invoke-GraphRequest "/devicemanagement/templates/$($obj.TemplateId)/categories/$($category.Id)/settingDefinitions" -ODataMetadata "minimal").Value
#$global:intentCategoryDefs.Add($category.Id,$catDefObjs)
# Get recommended settings for the category
$catRecommendedSettingsObj = (Invoke-GraphRequest "/deviceManagement/templates/$($obj.TemplateId)/categories/$($category.Id)/RecommendedSettings?" -ODataMetadata "minimal" @params).Value
$global:catRecommendedSettings.Add($category.Id, $catRecommendedSettingsObj)
}
$catDefIds = ($category.settingDefinitions | Select Id).Id
foreach($settingObj in $settings) #($settings | Where { $_.definitionId -in $catDefIds }))
{
Get-IntentSettingInfo $settingObj $category $settingObj.definitionId $settings
}
}
foreach($objSetting in ($script:objectSettings | Where { $_.ParentId -eq $null -and ($_.Dependecies | measure).Count -eq 0 }))
{
#if($objSetting.Dependecies) { continue }
Add-IntentSettingObjectToList $objSetting
}
}
function Add-IntentSettingObjectToList
{
param($objSetting)
if(($script:objectSettingsData | Where Id -eq $objSetting.Id)) { return }
$passConstraint = $true
foreach($dependencyObj in $objSetting.SettingDefinition.dependencies)
{
$dependencyItemObj = ($script:objectSettings | Where { $_.SettingDefinition.Id -eq $dependencyObj.definitionId })
if($dependencyObj.constraints.Count -gt 0)
{
foreach($constraint in $dependencyObj.constraints)
{
if($constraint.'@odata.type' -eq "#microsoft.graph.deviceManagementSettingBooleanConstraint")
{
if($dependencyItemObj.RawValue -eq $null -and $constraint.value -eq $false -or
($dependencyItemObj.RawValue -and $dependencyItemObj.RawValue.ToString() -ne $constraint.value.ToString()))
{
$passConstraint = $false
break
}
}
elseif($constraint.'@odata.type' -eq "#microsoft.graph.deviceManagementEnumConstraint")
{
if(-not ($constraint.values | Where Value -eq $dependencyItemObj.RawValue))
{
$passConstraint = $false
break
}
}
elseif($constraint.'@odata.type' -eq "#microsoft.graph.deviceManagementSettingIntegerConstraint")
{
if($dependencyItemObj.RawValue -ge $constraint.minimumValue -and $dependencyItemObj.RawValue -le $constraint.maximumValue)
{
$passConstraint = $false
break
}
}
}
}
else
{
$passConstraint = ($dependencyItemObj.RawValue -ne $null -and $dependencyItemObj.RawValue.ToString() -ne "NotConfigured" -and $dependencyItemObj.RawValue.ToString() -ne "False")
}
if(-not $passConstraint) { break ]}
}
if(-not $passConstraint)
{
return
}
$script:objectSettingsData += $objSetting
if($objSetting.ValueSet -eq $false) { return }
foreach($depObj in ($script:objectSettings | Where { $_.Dependecies.definitionId -eq $objSetting.SettingDefinition.Id }))
{
Add-IntentSettingObjectToList $depObj
}
foreach($depObj in ($script:objectSettings | Where { $_.ParentId -eq $objSetting.Id -and ($_.Dependecies | measure).Count -eq 0} ))
{
Add-IntentSettingObjectToList $depObj
}
}
function Get-IntentSettingInfo
{
param($valueObj, $category, $defId, $allSettings, [switch]$SkipConvertValue, [switch]$PassThru, $parentDef = $null)
$defObj = $category.settingDefinitions | Where id -eq $defId
if(-not $defObj) { return } # Should never happen!
$itemValue = $null
if($SkipConvertValue -ne $true)
{
$rawValue = $valueObj.valueJson | ConvertFrom-Json
}
else
{
$rawValue = $valueObj
}
$valueSet = Get-IsIntentObjectConfigured $rawValue
if($valueSet -eq $false)
{
; # Skip child settings
}
elseif($valueObj.'@odata.type' -eq '#microsoft.graph.deviceManagementCollectionSettingInstance' -or
$defObj.'@odata.type' -eq '#microsoft.graph.deviceManagementComplexSettingDefinition' -or
$defObj."valueType" -eq "collection")
{
$valueArr = @()
if($defObj.elementDefinitionId)
{
# Get the element defenition of the items in the collection
$elementDefObj = $category.settingDefinitions | Where id -eq $defObj.elementDefinitionId
}
else
{
$elementDefObj = $defObj
}
if($elementDefObj.propertyDefinitionIds)
{
# Elements are Complex and based of one or more definitions
foreach($tmpValue in $rawValue)
{
$arrValue = ""
foreach($propertyDefinitionId in $elementDefObj.propertyDefinitionIds)
{
$propDefObj = $category.settingDefinitions | Where id -eq $propertyDefinitionId
if($propDefObj.elementDefinitionId)
{
# The collection element can be based of multiple definitions with specific element types
$propDefObj = $category.settingDefinitions | Where id -eq $propDefObj.elementDefinitionId
}
if($arrValue) { $arrValue = ($arrValue + $script:propertySeparator)}
$propName = $propertyDefinitionId.Split('_')[-1]
$propValue = @()
foreach($childTmpVales in $tmpValue.$propName)
{
$propValue += Get-IntentObjectValue $propDefObj $childTmpVales
}
$arrValue = ($arrValue + ($propValue -join $script:propertySeparator))
}
$valueArr += $arrValue
}
}
elseif($rawValue)
{
# Elements are Strings, Integers etc.
foreach($tmpValue in $rawValue)
{
$valueArr += (Get-IntentObjectValue $elementDefObj $tmpValue)
}
}
if($valueArr.Count -gt 0)
{
$itemValue = $valueArr -join $script:objectSeparator # Or `n or?
}
$valueSet = $valueArr.Count -gt 0
}
elseif($valueObj.'@odata.type' -eq '#microsoft.graph.deviceManagementAbstractComplexSettingInstance' -or
$defObj.'@odata.type' -eq '#microsoft.graph.deviceManagementAbstractComplexSettingDefinition')
{
$tmpDef = $category.settingDefinitions | Where id -eq $rawValue.implementationId
if($tmpDef)
{
$itemValue = $tmpDef.displayName
}
else
{
$valueSet = $false
}
}
else
{
$itemValue = Get-IntentObjectValue $defObj $rawValue
if(-not $itemValue)
{
$valueSet = $false
}
}
if($valueSet -eq $false)
{
$itemValue = Get-LanguageString "SettingDetails.notConfigured"
}
elseif(-not $itemValue)
{
$itemValue = $rawValue
}
$curObjectInfo = New-Object PSObject -Property @{
Name=$defObj.displayName
Description=$defObj.description
Category=$category.displayName
CategoryDescription=$category.description # Will not have a description
Value=$itemValue
RawValue=$rawValue
CategoryObject=$category
SettingDefinition = $defObj
Dependecies = $defObj.dependencies
ValueSet = $valueSet
Id=[Guid]::NewGuid() #(([Guid]::NewGuid()).Guid)
ParentId = $null
SettingId = $defObj.Id # ToDo: Must have parent Id as well e.g. Firewall settings in Win10 baseline
ParentSettingId = $parentDef.Id
}
$script:objectSettings += $curObjectInfo
if($valueSet -eq $false)
{
; # Skip children if value is not set...
}
elseif($valueObj.'@odata.type' -eq '#microsoft.graph.deviceManagementComplexSettingInstance' -or
$defObj.'@odata.type' -eq '#microsoft.graph.deviceManagementComplexSettingDefinition')
{
if($valueObj.Value)
{
$isValueSet = $false
$sortByDefinitionIds = $true # !!!
if($sortByDefinitionIds -eq $true -and $defObj.propertyDefinitionIds)
{
foreach($childDefId in $defObj.propertyDefinitionIds)
{
$childSetting = $valueObj.Value | Where DefinitionId -eq $childDefId
if($childSetting)
{
$objValueInfo = Get-IntentSettingInfo $childSetting $category $childSetting.definitionId $allSettings -PassThru -parentDef $defObj
$objValueInfo.ParentId = $curObjectInfo.Id
if($objValueInfo.RawValue -is [Boolean] -and $objValueInfo.RawValue -eq $true)
{
$isValueSet = $true
}
elseif($objValueInfo.RawValue -is [String] -and -not [String]::IsNullOrEmpty($objValueInfo.RawValue) -and $objValueInfo.RawValue -ne "notConfigured" -and -not [String]::IsNullOrEmpty($objValueInfo.Value))
{
$isValueSet = $true
}
elseif($objValueInfo.RawValue -isnot [Boolean] -and $objValueInfo.RawValue -isnot [String])
{
$isValueSet = $true
}
}
}
}
else
{
foreach($childSetting in $valueObj.Value)
{
$objValueInfo = Get-IntentSettingInfo $childSetting $category $childSetting.definitionId $allSettings -PassThru -parentDef $defObj
$objValueInfo.ParentId = $curObjectInfo.Id
}
}
}
elseif($rawValue -and $defObj.propertyDefinitionIds)
{
$isValueSet = $false
$isDefault = $true
foreach($childDefId in $defObj.propertyDefinitionIds)
{
$propName = $childDefId.Split('_')[-1]
$objValueInfo = Get-IntentSettingInfo $rawValue.$propName $category $childDefId $allSettings -SkipConvertValue -PassThru -parentDef $defObj
if($objValueInfo.ValueSet -eq $true) { $isValueSet = $true }
if($objValueInfo.SettingDefinition.constraints -and
$objValueInfo.SettingDefinition.constraints[0].'@odata.type' -eq "#microsoft.graph.deviceManagementEnumConstraint" -and
($objValueInfo.SettingDefinition.constraints[0].values | measure).Count -gt 0)
{
if($objValueInfo.SettingDefinition.constraints[0].values[0].value -ne $rawValue.$propName)
{
$isDefault = $false
}
}
elseif($objValueInfo.SettingDefinition.valueType -eq "string")
{
if($null -ne $rawValue.$propName)
{
$isDefault = $false
}
}
elseif($objValueInfo.SettingDefinition.valueType -eq "boolean")
{
if($false -ne $rawValue.$propName)
{
$isDefault = $false
}
}
$objValueInfo.ParentId = $curObjectInfo.Id
}
if($isDefault)
{
# All child items are using default settings so set value not set
# ToDo: Verify ALL cases...don't like this but it looks like this is how it's done
$isValueSet = $false
}
}
else
{
$isValueSet = $false
}
$curObjectInfo.Value = ?: $isValueSet "Configure" (Get-LanguageString "SettingDetails.notConfigured")
$curObjectInfo.ValueSet = $isValueSet
}
elseif(($valueObj.'@odata.type' -eq '#microsoft.graph.deviceManagementAbstractComplexSettingInstance' -or
$defObj.'@odata.type' -eq '#microsoft.graph.deviceManagementAbstractComplexSettingDefinition') -and
$rawValue -and $tmpDef)
{
foreach($childDefId in $tmpDef.propertyDefinitionIds)
{
$propName = $childDefId.Split('_')[-1]
$objValueInfo = Get-IntentSettingInfo $rawValue.$propName $category $childDefId $allSettings -SkipConvertValue -PassThru -parentDef $defObj
$objValueInfo.ParentId = $curObjectInfo.Id
}
}
if($PassThru)
{
$curObjectInfo
}
}
function Get-IntentObjectValue
{
param($defObj, $rawValue)
$itemValue = $null
if($defObj.constraints.'@odata.type' -eq "#microsoft.graph.deviceManagementEnumConstraint")
{
$tmpOption = $defObj.constraints.Values | Where value -eq $rawValue
if(-not $tmpOption -and $rawValue -eq $null)
{
# This is NOT ok. There is no defaultValue of the setting definitions
# Ex when this is used:
# deviceConfiguration--windows10EndpointProtectionConfiguration_defenderScanDirection
$tmpOption = $defObj.constraints.Values[0]
}
$itemValue = $tmpOption.displayName
}
elseif($defObj.valueType -eq "boolean")
{
if($rawValue -eq "True")
{
$itemValue = Get-LanguageString "SettingDetails.yes"
}
}
else
{
$itemValue = $rawValue
}
$itemValue
}
function Get-IsIntentObjectConfigured
{
param($obj)
# Custom checks if needed
return $true
}
#endregion
#region Profile Types e.g. DeviceConfiguration Profiles, Compliance Policies etc
function Invoke-TranslateProfileObject
{
param($obj, $objectType)
$objInfo = Get-TranslationFiles $obj.'@OData.Type'
if(-not $objInfo)
{
return $false
}
elseif(($objInfo | measure).Count -gt 1)
{
Write-Log "Multiple category types returned for $($obj.'@OData.Type'). Aborting..." 3
return $false
}
Add-BasicDefaultValues $obj $objectType
Add-BasicAdditionalValues $obj $objectType
$allFiles = @()
if($objInfo.Categories)
{
foreach($objCategory in ($objInfo.Categories))
{
$file = ($global:AppRootFolder + "\Documentation\ObjectInfo\$($objCategory)_$($objInfo.PolicyType).json")
$fi = [IO.FileInfo]$file
if($fi.Exists -eq $false)
{
Write-Log "Category file '$file' not found"
continue
}
$allFiles += $fi
}
}
else
{
# Shuld only be one file. Compliance policies might have more
$files = [IO.Directory]::EnumerateFiles($global:AppRootFolder + "\Documentation\ObjectInfo", "*_$($objInfo.PolicyType).json")
if(($files | measure).Count -eq 0)
{
Write-Log "No category files returned for $($objInfo.PolicyType)" 2
}
elseif(($files | measure).Count -gt 1)
{
Write-Log "Multiple category files returned for $($objInfo.PolicyType)" 2
}
foreach($file in $files)
{
$fi = [IO.FileInfo]$file
$allFiles += $fi
}
}
Add-CustomProfileProperties $obj
foreach($fi in $allFiles)
{
try
{
$categoryObj = (Get-Content $fi.FullName -Encoding UTF8) | ConvertFrom-Json
$script:CurrentSubCategory = ""
Invoke-TranslateSection $obj $categoryObj."$($fi.BaseName)" $objInfo
}
catch
{
Write-LogError "Failed tp translate file $($fi.Name)" $_.Exception
}
}
return $true
}
function Get-LanguageString
{
param($string, $defaultValue = $null, [switch]$IgnoreMissing)
if(-not $script:languageStrings)
{
$fileContent = Get-Content ($global:AppRootFolder + "\Documentation\Strings-$($script:DocumentationLanguage).json") -Encoding UTF8
$script:languageStrings = $fileContent | ConvertFrom-Json
}
if(!$string) { return }
$arrParts = $string.Split('.')
if([String]::IsNullOrEmpty($arrParts[-1])) { return }
$obj = $script:languageStrings
foreach($part in $arrParts.Split('.'))
{
if($obj.$part -or $part -eq "Empty")
{
$obj = $obj.$part
}
else
{
if($part -and $IgnoreMissing -ne $true)
{
Write-Host "Could not find string $string. Part '$part' was not found"
}
return $defaultValue
}
}
return $obj.Trim("`n")
}
function Get-TranslationFiles
{
param($type)
if(-not $script:ObjectPolicyTypes)
{
$script:ObjectPolicyTypes = Get-Content ($global:AppRootFolder + "\Documentation\ObjectCategories.json") | ConvertFrom-Json
}
return $script:ObjectPolicyTypes | Where ObjectType -eq $type
}
function Get-Category
{
param($categoryId)
if($categoryId -is [String])
{
return Get-LanguageString (?: $categoryId.Contains(".") $categoryId "Category.$($categoryId)")
}
if(-not $script:Categories)
{
$script:CategoryIds = Get-Content ($global:AppRootFolder + "\Documentation\CategoryId.json") -Encoding UTF8 | ConvertFrom-Json
}
Get-LanguageString "Category.$($script:CategoryIds."$($categoryId)")"
}
function Invoke-VerifyCondition
{
param($obj, $prop, $objInfo)
if(!$prop.Condition -or ($prop.Condition.Expressions | measure).Count -eq 0) { return $true}
# Default condition type is or
$type = ?: ($prop.Condition.type -eq "and") "and" "or"
$defaultReturn = ?: ($type -eq "and") $true $false
foreach($expression in $prop.Condition.Expressions)
{
if(!$expression.property) { continue }
$tmpProp = $obj.PSObject.Properties | Where Name -eq $expression.property
if(!$tmpProp)
{
if($expression.ignoreMissing -eq $true) { continue }
return $false
}
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 = $null -ne $tmpProp.Value
}
elseif($expression.operator -eq "ne")
{
$tmpRet = $obj."$($expression.property)" -ne $expression.value
}
elseif($expression.operator -eq "gt")
{
$tmpRet = $obj."$($expression.property)" -gt $expression.value
}
elseif($expression.operator -eq "ge")
{
$tmpRet = $obj."$($expression.property)" -ge $expression.value
}
elseif($expression.operator -eq "lt")
{
$tmpRet = $obj."$($expression.property)" -lt $expression.value
}
elseif($expression.operator -eq "le")
{
$tmpRet = $obj."$($expression.property)" -le $expression.value
}
elseif($expression.operator -eq "like")
{
$tmpRet = $obj."$($expression.property)" -like $expression.value
}
elseif($expression.operator -eq "notlike")
{
$tmpRet = $obj."$($expression.property)" -notlike $expression.value
}
else
{
# Default operator is eq
$tmpRet = $obj."$($expression.property)" -eq $expression.value
}
if($tmpRet -eq $true -and $type -eq "or") { return $true }
if($tmpRet -eq $false -and $type -eq "and") { return $false }
}
return $defaultReturn
}
function Invoke-TranslateSection
{
param($obj, $sectionObject, $objInfo, $parent = $null)
foreach($prop in $sectionObject)
{
$valueData = $null
$value = $null
$valueSet = $false
$useParentProp = $false
#if($prop.enabled -eq $false -and $objInfo.ShowDisabled -ne $true) { continue }
if((Invoke-VerifyCondition $obj $prop $objInfo) -eq $false)
{
Write-LogDebug "Condition returned false: $(($prop.Condition | ConvertTo-Json -Depth 10 -Compress))" 2
continue
}
$obj = Get-CustomPropertyObject $obj $prop
$rawValue = $obj."$($prop.entityKey)"
if($prop.dataType -eq 8)
{
if($prop.nameResourceKey -eq "LearnMore") { continue }
elseif($prop.nameResourceKey -in $script:categoriesToIgnore) { continue }
elseif($prop.nameResourceKey)
{
$tmpStr = (Get-LanguageString (?: $prop.nameResourceKey.Contains(".") $prop.nameResourceKey "SettingDetails.$($prop.nameResourceKey)"))
if($tmpStr -and $tmpStr.Length -lt 75) # !!! categoriesToIgnore will take care of some
{
$script:CurrentSubCategory = $tmpStr
}
elseif($tmpStr)
{
Write-LogDebug "SubCategpry ignored based on length: $tmpStr" 2
}
}
Invoke-ChildSections $obj $prop
continue
}
elseif($prop.dataType -eq 5) # complex Options
{
# Skip if disabled
if($prop.enabled -eq $false -and $objInfo.ShowDisabled -ne $true) { continue }
# Set sub-category to nameResourceKey value if EntityKey is empty?
if(-not $prop.EntityKey -and $prop.nameResourceKey)
{
$script:CurrentSubCategory = (Get-LanguageString (?: $prop.nameResourceKey.Contains(".") $prop.nameResourceKey "SettingDetails.$($prop.nameResourceKey)"))
}
foreach($tmpObj in $obj)
{
Invoke-TranslateSection $tmpObj $prop.complexOptions $objInfo -Parent $prop
}
continue
}
elseif($prop.dataType -eq 6) # Complex option based on sub property
{
# Skip if disabled
if($prop.enabled -eq $false -and $objInfo.ShowDisabled -ne $true) { continue }
# Use sub property if EntityKey is specified
$propObj = $null
if($prop.entityKey)
{
$propObj = $obj.PSObject.Properties | Where Name -eq $prop.entityKey
}
foreach($tmpObj in (?: ($propObj -ne $null) $rawValue $obj))
{
Invoke-TranslateSection $tmpObj $prop.complexOptions $objInfo -Parent $prop
}
continue
}
elseif($prop.dataType -eq 9) # Label. Skip the label but add children
{
Invoke-ChildSections $obj $prop
continue
}
elseif($prop.dataType -eq 10) # Information box
{
continue
}
elseif($prop.dataType -eq 101) # Static label. Language string id in value property
{
if($prop.value)
{
$value = Get-LanguageString $prop.value
Add-PropertyInfo $prop $value $rawValue $rawValue
}
}
elseif([String]::IsNullOrEmpty($prop.entityKey) -eq $false)
{
$valueSet = ($rawValue -ne $null)
$skipChildren = $false
if($rawValue -eq $null -and ![String]::IsNullOrEmpty($prop.unconfiguredValue) -and $global:chkSetUnconfiguredValue.IsChecked)
{
$propValue = $prop.unconfiguredValue
Add-NotConfiguredProperty $prop
}
elseif($rawValue -eq $null -and ![String]::IsNullOrEmpty($prop.defaultValue) -and $global:chkSetDefaultValue.IsChecked)
{
$propValue = $prop.defaultValue
}
elseif($rawValue -eq $null -and ![String]::IsNullOrEmpty($prop.emptyValueResourceKey) -and $global:chkSetDefaultValue.IsChecked)
{
$propValue = Get-LanguageString $prop.emptyValueResourceKey
}
else
{
$propValue = $rawValue
}
$addPropertyInfo = $true
$customValue = Get-CustomProfileValue $obj $prop
if($customValue -is [Boolean] -and $customValue -eq $false)
{
continue # Property added by Custom info. Skip processing
}
elseif(-not $customValue)
{
# Some properties are added but remarked to let it fall through to not supported handler
# Do linked property BEFORE property detection
# Linked properties are based on navigationLinks so they are not an actual property
if($prop.dataType -eq 4) # Linked certificate
{
$useParentProp = $true
# Use $script:currentObject since $obj could be a property on the original object
# Cert links are always specified on the main object
$cert = Invoke-GraphRequest -URL $script:currentObject."$($prop.entityKey)@odata.navigationLink" -ODataMetadata "minimal" -NoError
if($cert)
{
if($cert.value -is [Object[]])
{
$certs = @()
$cert.value | ForEach-Object { $certs += $_.displayName }
if($certs.Count -gt 0)
{
$value = ($certs -join $script:objectSeparator)
}
}
elseif($cert.displayName)
{
$value = $cert.displayName
}
$rawValue = $value
}
elseif($script:currentObject.'@ObjectFromFile' -eq $true)
{
$value = "##TBD - Linked Certificate"
$rawValue = $value
}
}
elseif($prop.dataType -eq 200) # Multi option based on boolean value
{
$value = Get-LanguageString $prop.entityKey
}
elseif(($prop.allowMissing -ne $true) -and
($prop.entityKey -ne ".") -and
(-not ($obj.PSObject.Properties | Where Name -eq $prop.entityKey)) -and
(-not ($obj.PSObject.Properties | Where Name -eq "$($prop.entityKey)@odata.navigationLink")))
{
if($prop.enabled -ne $false)
{
Write-Log "Property with EntityKey $($prop.entityKey) is missing. Property will not be added!" 2
}
else
{
Write-LogDebug "Disabled property with EntityKey $($prop.entityKey) is missing. Property will not be added!" 2
}
continue
}
elseif($prop.dataType -eq 0) # Boolean
{
$value = Invoke-TranslateBoolean $obj $prop
}
elseif($prop.dataType -eq 1) # Base64 e.g. certificate data
{
$value = $obj."$((?? $prop.filenameEntityKey $prop.EntityKey))"
$valueData = $obj."$((?? $prop.dataEntityKey $prop.EntityKey))"
}
elseif($prop.dataType -eq 2) # Multiline string e.g. XML file
{
$value = $obj."$((?? $prop.filenameEntityKey $prop.EntityKey))"
$valueData = $obj."$((?? $prop.dataEntityKey $prop.EntityKey))"
}
elseif($prop.dataType -eq 3) # Image
{
# Better text?
$valueData = $propValue
$value = ?: $propValue "Image file" $null
}
elseif($prop.dataType -eq 7) # omaSettingDateTime
{
$value = $propValue # Might require formatting
}
elseif($prop.dataType -eq 9) # DataType
{
continue # Ignore @odata.type labels
}
elseif($prop.dataType -eq 10) # Label
{
continue # Ignore labels
}
elseif($prop.dataType -eq 11) # App Picker
{
#$value = $propValue
}
elseif($prop.dataType -eq 12) # Multiline String? Sometimes and array and sometimes a binary
{
if(($propValue | measure).Count -gt 0)
{
$value = $propValue -join $script:objectSeparator
}
}
elseif($prop.dataType -eq 13) # Multi option
{
$value = Invoke-TranslateMultiOption $obj $prop
}
elseif($prop.dataType -eq 14) # Int32
{
$value = $propValue
}
elseif($prop.dataType -eq 15) # Int64
{
$value = $propValue
}
elseif($prop.dataType -eq 16) # Option
{
Invoke-TranslateOption $obj $prop | Out-Null
continue
}
elseif($prop.dataType -eq 19) # Option
{
Invoke-TranslateOption $obj $prop | Out-Null
continue
}
elseif($prop.dataType -eq 20) # String
{
$value = $propValue
}
elseif($prop.dataType -eq 21) # Table
{
$value = Invoke-TranslateTable $obj $prop
continue
}
elseif($prop.dataType -eq 22) # Scale value e.g. 4 Years (Certificate validity period)
{
$value = $propValue
$scaleEntityKey = ?? $obj."$($prop.scaleEntityKey)" $prop.defaultScale
if($scaleEntityKey)
{
$scaleOption = $prop.scaleOptions | Where value -eq $scaleEntityKey
if($scaleOption.nameResourceKey)
{
$value = "{0} {1}" -f $propValue, (Get-LanguageString "SettingDetails.$($scaleOption.nameResourceKey)")
}
}
}
#elseif($prop.dataType -eq 25) {}# Home screen
# All data types above 100 are custom
elseif($prop.dataType -eq 100) # Custom Edm.Duration
{
$value = Invoke-TranslateDuration $obj $prop
}
elseif($prop.dataType -eq 102) # Culture name
{
$value = Get-CutureLanguageString (?? $propValue $prop.unconfiguredValue)
}
elseif($prop.dataType -eq 103) # Boolean action but hide children on false
{
$value = Invoke-TranslateBoolean $obj $prop
$skipChildren = ($propValue -eq $false)
}
elseif($prop.dataType -eq 104) # Multi option based on boolean value
{
$value = Invoke-TranslateMultiOptionBoolean $obj $prop
$skipChildren = ($propValue -eq $false)
}
elseif($prop.dataType -eq 105) # Multi option based on boolean value but $false if the value is selected
{
$value = Invoke-TranslateMultiOptionBoolean $obj $prop $false
$skipChildren = ($propValue -eq $false)
}
elseif($prop.dataType -eq 106) # Array of languages (Culture)
{
$arrTmp = @()
foreach($tmpLng in $propValue)
{
$arrTmp += Get-CutureLanguageString $tmpLng
}
$value = $arrTmp -join $script:objectSeparator
}
else
{
Write-Log "Unsupported property '$((Get-LanguageString "SettingDetails.$($prop.nameResourceKey)"))' ($($prop.nameResourceKey)) for object property $($prop.entityKey). Type: $($prop.dataType)" 2
$value = $propValue
}
}
else
{
$value = $customValue.Value
$rawValue = $customValue.RawValue
$valueSet = ($rawValue -ne $null)
$addPropertyInfo = $customValue.AddPropertyInfo
}
if($addPropertyInfo)
{
Add-PropertyInfo (?: ($useParentProp -and $parent) $parent $prop) $value $rawValue
}
}
else
{
Write-Log "No property entity key: $($prop.dataType) ($($prop.nameResourceKey))"
}
if($valueSet -and $skipChildren -ne $true)
{
Invoke-ChildSections $obj $prop
}
}
}
function Get-CutureLanguageString
{
param($culture)
if(!$culture)
{
return
}
try
{
if($culture -eq "os-default")
{
return Get-LanguageString "Autopilot.OOBE.useOSDefaultLanguage"
}
else
{
if(!$script:languageStrings) { Get-LanguageString } # This will load the language strings
if($script:languageStrings.Languages.$culture)
{
return $script:languageStrings.Languages.$culture
}
$lngArr = $culture.Split('-')
if($lngArr.Length -eq 3)
{
if($script:languageStrings.Languages."$(($lngArr[0]-$lngArr[1]))")
{
return $script:languageStrings.Languages."$(($lngArr[0]-$lngArr[1]))"
}
}
if($lngArr.Length -gt 1 -and $script:languageStrings.Languages."$(($lngArr[0]))")
{
return $script:languageStrings.Languages."$(($lngArr[0]))"
}
Write-Log "Translated language for $culture not found" 2
([cultureinfo]$culture).EnglishName
}
}
catch{}
}
function Add-PropertyInfo
{
param($prop, $value, $originalValue, $jsonValue)
if($prop.Category -eq "1000")
{
Add-BasicPropertyValue (Get-LanguageString $prop.nameResourceKey) $value
return
}
$script:objectSettingsData += Get-PropertyInfo $prop $value $originalValue $jsonValue
Invoke-CustomPostAddValue $prop
}
function Add-PropertyInfoObject
{
param($propInfo)
if($propInfo -eq $null) { return }
$script:objectSettingsData += $propInfo
}
function Get-PropertyInfo
{
param($prop,$value,$originalValue, $jsonValue)
if($prop.nameResource)
{
$name = $prop.nameResource
}
else
{
$name = Get-LanguageString (?: $prop.nameResourceKey.Contains(".") $prop.nameResourceKey "SettingDetails.$($prop.nameResourceKey)")
}
$description = ""
if($prop.descriptionResource)
{
$description = $prop.descriptionResource
}
elseif($prop.descriptionResourceKey)
{
$description = Get-LanguageString (?: $prop.descriptionResourceKey.Contains(".") $prop.descriptionResourceKey "SettingDetails.$($prop.descriptionResourceKey)")
}
$categoryStr = $null
if($prop.category)
{
$categoryStr = Get-Category $prop.category
}
if(!$jsonValue -and $null -ne $rawValue -and "$($rawValue)" -ne "")
{
$jsonValue = $rawValue | ConvertTo-Json -Depth 10 -Compress
}
if($prop.emptyValueResourceKey)
{
$defValue = Get-LanguageString $prop.emptyValueResourceKey
}
else
{
$defValue = $prop.defaultValue
}
return New-Object PSObject -Property @{
Name=$name
Description=$description
Value=$value
Category=$categoryStr
SubCategory=$script:CurrentSubCategory
Property=$prop.entityKey
ValueSet=$valueSet
DataType=$prop.dataType
RawValue=$originalValue
RawJsonValue=$jsonValue
DefaultValue=$defValue
UnconfiguredValue=$prop.unconfiguredValue
Enabled=$prop.Enabled
EntityKey=$prop.EntityKey
}
}
function Invoke-ChildSections
{
param($obj, $sectionObject)
$objTmp = Get-CustomChildObject $obj $sectionObject
Invoke-TranslateSection $objTmp $sectionObject.Children $objInfo -Parent $sectionObject
Invoke-TranslateSection $objTmp $sectionObject.ChildSettings $objInfo -Parent $sectionObject
}
function Get-CustomProfileValue
{
param($obj, $prop)
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.GetCustomProfileValue)
{
$retObj = & $docProvider.GetCustomProfileValue $obj $prop $script:currentObject $script:propertySeparator $script:objectSeparator
if($retObj -ne $null)
{
return $retObj
}
}
}
}
function Invoke-CustomPostAddValue
{
param($prop)
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.PostAddValue)
{
$retObj = & $docProvider.PostAddValue $script:currentObject $prop $script:propertySeparator $script:objectSeparator
if($retObj -ne $null)
{
return $retObj
}
}
}
}
function Get-CustomPropertyObject
{
param($obj, $prop)
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.GetCustomPropertyObject)
{
# $script:currentObject is used to always send in the main object
$retObj = & $docProvider.GetCustomPropertyObject $script:currentObject $prop
if($retObj)
{
return $retObj
}
}
}
return $obj
}
function Get-CustomChildObject
{
param($obj, $prop)
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.GetCustomChildObject)
{
# $script:currentObject is used to always send in the main object
$retObj = & $docProvider.GetCustomChildObject $script:currentObject $prop
if($retObj)
{
return $retObj
}
}
}
return $obj
}
function Add-CustomProfileProperties
{
param($obj)
foreach($docProvider in ($global:documentationProviders | Sort -Property Priority))
{
if($docProvider.AddCustomProfileProperty)
{
if((& $docProvider.AddCustomProfileProperty $obj $script:propertySeparator $script:objectSeparator))
{
return
}
}
}
}
function Get-CustomIgnoredCategories
{
param($obj)
$script:categoriesToIgnore = @(
#microsoft.graph.windows10EndpointProtectionConfiguration
"defenderSecurityCenterContactOptionsText",
"globalConfigurationsDescription","generalNetworkSettingsHeader",
"firewallCreateRules","exploitGuardCFHeadingText","exploitGuardNFTitle",
"exploitGuardEPExplainationPart1","exploitGuardEPExplainationPart2","exploitGuardEPExplainationPart3","exploitGuardEPExplainationPart4",
"defenderSecurityCenterSubHeaderText","defenderSecurityCenterITContactInformationSubHeaderText",
"windows10EndpointProtectionDeviceGuardLearnMore",
#microsoft.graph.windows10GeneralConfiguration
"win10DefaultPrivacyHeader",
"dfciBuiltinHeaderDescName"
)
}
function Add-DefenderFirewallSettings
{
param($fwSettings, $fwType)
foreach($fwProp in ($fwSettings.PSObject.Properties | Where { $_.Name -like "*Blocked" -or $_.Name -like '*NotMerged' }).Name)
{
if($fwProp -like "*Blocked")
{
$blockedValue = $fwSettings.$fwProp
$propPre = $fwProp.SubString(0, $fwProp.Length - 7)
if(($fwSettings.PSObject.Properties | Where { $_.Name -eq "$($propPre)Required" }))
{
$nonBlockedValue = $fwSettings."$($propPre)Required"
}
elseif(($fwSettings.PSObject.Properties | Where { $_.Name -eq "$($propPre)Allowed" }))
{
$nonBlockedValue = $fwSettings."$($propPre)Allowed"
}
else { continue }
$fwPropName = "firewallSyntheticProfile$($fwType)$($propPre)"
}
else
{
$blockedValue = $fwSettings.$fwProp
$propPre = $fwProp.SubString(0, $fwProp.Length - 9)
if(($fwSettings.PSObject.Properties | Where { $_.Name -eq "$($propPre)Merged" }))
{
$nonBlockedValue = $fwSettings."$($propPre)Merged"
}
else { continue }
$fwPropName = "firewallSyntheticProfile$($fwType)$($propPre)Merge"
}
$fwValue = "notConfigured"
if($blockedValue -eq $true -and $nonBlockedValue -eq $false)
{
$fwValue = "blocked"
}
elseif($blockedValue -eq $false -and $nonBlockedValue -eq $true)
{
$fwValue = "allowed"
}
$obj | Add-Member Noteproperty -Name $fwPropName -Value $fwValue -Force
}
}
function Get-ConditionalLaunchSetting
{
param($obj, $lngId, $prop, $actionLngId, [switch]$SkipValue)
if($obj."$($prop)" -eq $null -or ($obj."$($prop)" -is [String] -and $obj."$($prop)" -eq "notConfigured")) { return }
elseif($obj."$($prop)" -is [String] -and $obj."$($prop)".StartsWith("P"))
{
$ts = Get-DurationValue $obj."$($prop)" -ReturnTimeSpan
if($prop -eq "periodOfflineBeforeAccessCheck")
{
$settingValue = $ts.TotalMinutes
}
else
{
$settingValue = $ts.TotalDays
}
}
elseif($SkipValue -ne $true)
{
$settingValue = $obj."$($prop)"
}
else
{
$settingValue = $null
}
[PSCustomObject]@{
Setting=(Get-LanguageString (?: $lngId.Contains(".") $lngId "SettingDetails.$lngId"))
Value=$settingValue
Action=(Get-LanguageString (?: $actionLngId.Contains(".") $actionLngId "SettingDetails.$actionLngId"))
}
}
function Invoke-TranslateBoolean
{
param($obj, $prop)
$propValue = (?? ($obj."$($prop.entityKey)") $prop.unconfiguredValue)
if($propValue -is [String] -and ($propValue -eq "true" -or $propValue -eq "false"))
{
$propValue = [Boolean]::Parse($propValue)
}
if("$propValue" -eq "notConfigured")
{
Get-LanguageString "BooleanActions.notConfigured"
}
elseif($prop.booleanActions -eq 0 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.allow"
}
elseif($prop.booleanActions -eq 1 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.require"
}
elseif($prop.booleanActions -eq 2 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.enable"
}
elseif($prop.booleanActions -eq 3 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.block"
}
elseif($prop.booleanActions -eq 4 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.configured"
}
elseif($prop.booleanActions -eq 5 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.disable"
}
elseif($prop.booleanActions -eq 6 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.limit"
}
elseif($prop.booleanActions -eq 7 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.show"
}
elseif($prop.booleanActions -eq 8 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.hide"
}
elseif($prop.booleanActions -eq 9 -and $propValue -eq $true)
{
Get-LanguageString "BooleanActions.yes"
}
# Custom
elseif($prop.booleanActions -eq 100)
{
Get-LanguageString (?: $propValue "BooleanActions.block" "BooleanActions.allow")
}
elseif($prop.booleanActions -eq 101)
{
Get-LanguageString (?: $propValue "BooleanActions.require" "SettingDetails.notRequired")
}
elseif($prop.booleanActions -eq 102)
{
Get-LanguageString (?: $propValue "BooleanActions.enable" "BooleanActions.disable")
}
elseif($prop.booleanActions -eq 107 -and $propValue -eq $true)
{
Get-LanguageString (?: $propValue "BooleanActions.show" "BooleanActions.hide")
}
elseif($prop.booleanActions -eq 108 -and $propValue -eq $true)
{
Get-LanguageString (?: $propValue "BooleanActions.hide" "BooleanActions.show")
}
elseif($prop.booleanActions -eq 109)
{
Get-LanguageString (?: $propValue "BooleanActions.yes" "SettingDetails.no")
}
elseif($prop.booleanActions -eq 110)
{
Get-LanguageString (?: $propValue "SettingDetails.no" "BooleanActions.yes")
}
elseif($prop.booleanActions -eq 120)
{
Get-LanguageString (?: $propValue "SettingDetails.onOption" "SettingDetails.offOption")
}
elseif($prop.booleanActions -eq 200)
{
Get-LanguageString (?: $propValue "BooleanActions.allow" "BooleanActions.block")
}
elseif($prop.booleanActions -eq 201)
{
Get-LanguageString (?: $propValue "SettingDetails.notRequired" "BooleanActions.require")
}
elseif($prop.booleanActions -eq 220)
{
Get-LanguageString (?: $propValue "SettingDetails.offOption" "SettingDetails.onOption")
}
else
{
Add-NotConfiguredProperty $prop
Get-LanguageString "BooleanActions.notConfigured"
}
}
function Add-NotConfiguredProperty
{
param($prop)
# Add not configured prop to the base object
if(-not ($script:currentObject.PSObject.Properties | Where Name -eq "@UnconfiguredProperties"))
{
$script:currentObject | Add-Member Noteproperty -Name "@UnconfiguredProperties" -Value @() -Force
}
$script:currentObject.'@UnconfiguredProperties' += $prop
}
function Invoke-TranslateOption
{
param($obj, $prop, [Switch]$SkipOptionChildren, $propValue = $null)
if(-not $propValue)
{
$propValue = $obj."$($prop.entityKey)"
}
if($obj.defenderSecurityCenterDisableRansomwareUI -eq $true)
{
$obj.defenderSecurityCenterDisableRansomwareUI = "blockOption"
}
foreach($option in $prop.options)
{
if("$propValue" -eq "$($option.Value)") # Compare strings since some properties is boolean but option value is string
{
if($option.nameResource)
{
$optionValue = $prop.nameResource
}
elseif($option.displayText)
{
$optionValue = $option.displayText
}
elseif($option.nameResourceKey)
{
if($option.nameResourceKey -eq "notConfigured")
{
Add-NotConfiguredProperty $prop
}
$optionValue = (Get-LanguageString (?: $option.nameResourceKey.Contains(".") $option.nameResourceKey "SettingDetails.$($option.nameResourceKey)"))
}
else
{
$optionValue = $option.Value
}
@{
Option=$option
Value=$optionValue
}
Add-PropertyInfo $prop $optionValue $propValue
if($SkipOptionChildren -ne $true)
{
Invoke-ChildSections (Get-CustomChildObject $obj $prop) $option
}
break
}
}
# This does not work. Sometime's boolean children are added on $true and sometimes on $false. Depends on the property type
# This WILL cause "disabled properties" in the documentation
# Log for now to identify how many properties it is
if($propValue -is [Boolean] -and $prop.ChildSettings.Count -gt 0)
{
Write-Log "Child properties for boolean $($prop.EntityKey) value=$($propValue) added. Disabled items might be included." 2
}
#if($propValue -isnot [Boolean] -or $propValue -eq $true)
#{
# # Skip Child Settings if the option value is not configured
Invoke-ChildSections $obj $prop
#}
}
function Invoke-TranslateMultiOption
{
param($obj, $prop)
if(($obj.PSObject.Properties | Where Name -eq $prop.entityKey))
{
$propValues = $obj."$($prop.entityKey)"
if($propValues -is [String])
{
$propValues = $propValues.Split(',')
}
}
elseif( $prop.entityKey -like "*List")
{
# Should NOT be used!
$tmpProp = $prop.entityKey.SubString(0, $prop.entityKey.Length - 4)
$propValues = $obj."$($tmpProp)"
}
$selectedValues = @()
foreach($propValue in $propValues)
{
$option = $prop.Options | Where Value -eq $propValue
if($option)
{
if($option.nameResource)
{
$selectedValues += $option.nameResource
}
else
{
$selectedValues += (Get-LanguageString (?: $option.nameResourceKey.Contains(".") $option.nameResourceKey "SettingDetails.$($option.nameResourceKey)"))
}
}
}
if($selectedValues.Count -gt 0)
{
($selectedValues -join $script:propertySeparator)
}
else
{
Add-NotConfiguredProperty $prop
Get-LanguageString "BooleanActions.notConfigured"
}
}
function Invoke-TranslateMultiOptionBoolean
{
param($obj, $prop, $selectedValue = $true)
$propObj = $obj."$($prop.entityKey)"
$selectedValues = @()
foreach($propValue in ($propObj.PSObject.Properties).Name)
{
if($propObj.$propValue -isnot [Boolean]) { continue }
$option = $prop.options | Where value -eq $propValue
if(-not $option) { continue }
if($propObj.$propValue -eq $selectedValue)
{
if($option.nameResource)
{
$selectedValues += $option.nameResource
}
else
{
$selectedValues += (Get-LanguageString (?: $option.nameResourceKey.Contains(".") $option.nameResourceKey "SettingDetails.$($option.nameResourceKey)"))
}
}
}
if($selectedValues.Count -gt 0)
{
($selectedValues -join $script:propertySeparator)
}
else
{
Get-LanguageString "BooleanActions.notConfigured"
}
}
function Invoke-TranslateTable
{
param($obj, $prop)
if($prop.entityKey -eq ".")
{
$propValue = $obj
}
else
{
$propValue = $obj."$($prop.entityKey)"
}
$items = @()
foreach($item in $propValue)
{
$itemValues = @()
foreach($column in $prop.Columns)
{
if($column.metadata.entityKey -eq "unusedForSingleItems")
{
$itemValues += $item
}
elseif($column.metadata.entityKey -eq $prop.entityKey -and ($prop.Columns | measure).Count -eq 1)
{
# Some tables has the same EntityKey for the table and the columen. That will generate the wrong value
$itemValues += $item
}
elseif(($prop.Columns | measure).Count -eq 1 -and $item."$($column.metadata.entityKey)" -eq $null -and $obj."$($column.metadata.entityKey)" -eq $null -and $item -is [String])
{
# Not sure how correct this is but some tables has one column with and EntityKey but the objects is a string list
$itemValues += $item
}
else
{
$itemValues += (?? $item."$($column.metadata.entityKey)" $obj."$($column.metadata.entityKey)")
}
}
if($prop.separator)
{
$items += $itemValues -join $prop.separator
}
else
{
$items += $itemValues -join $script:propertySeparator
}
}
if($items.Count -gt 0)
{
if((-not $prop.nameResourceKey -or $prop.nameResourceKey -eq "Empty") -and $prop.columns[0].metadata.nameResourceKey)
{
Add-PropertyInfo $prop.columns[0].metadata ($items -join $script:objectSeparator) $propValue
}
else
{
Add-PropertyInfo $prop ($items -join $script:objectSeparator) $propValue
}
}
else
{
if((-not $prop.nameResourceKey -or $prop.nameResourceKey -eq "Empty") -and $prop.Columns[0].metadata.nameResourceKey)
{
Add-PropertyInfo $prop.Columns[0].metadata $null
}
else
{
Add-PropertyInfo $prop $null
}
}
Invoke-ChildSections $obj $prop
}
function Invoke-TranslateDuration
{
param($obj, $prop)
(Get-DurationValue $obj."$($prop.entityKey)")
}
function Get-DurationValue
{ param($durationValue, [Switch]$ReturnTimeSpan)
if(-not $durationValue -or !$durationValue.StartsWith("P")) { return "0" }
$arr = @('P','T','Y','D','H','M','S')
$values = $durationValue.Split($arr)
$years = 0
$days = 0
$hours = 0
$minutes = 0
$seconds = 0
$i = 0
foreach($tmp in $arr)
{
if($durationValue.Contains($tmp))
{
if($ReturnTimeSpan -eq $true)
{
if($tmp -eq "Y") { $years = [int]$values[$i] }
elseif($tmp -eq "D") { $days = [int]$values[$i] }
elseif($tmp -eq "H") { $hours = [int]$values[$i] }
elseif($tmp -eq "M") { $minutes = [int]$values[$i] }
elseif($tmp -eq "S") { $seconds = [int]$values[$i] }
}
elseif(![String]::IsNullOrEmpty($values[$i])) { return $values[$i] }
$i++
}
}
if($ReturnTimeSpan -eq $true)
{
$days += ($years * 365) # Not really 100% true
[timespan]::new($days, $hours, $minutes, $seconds)
}
else
{
return "0"
}
}
function Add-Header
{
param($sectionObj, [switch]$Category)
if($sectionObj.nameResourceKey)
{
$script:CurrentSubCategory = (Get-LanguageString "SettingDetails.$($sectionObj.nameResourceKey)")
}
}
#endregion
#region
function Invoke-TranslateScheduledActionType
{
param($scheduleActionObjects)
$category = Get-LanguageString "Category.complianceActionsLabel"
foreach($actionRule in $scheduleActionObjects)
{
foreach($actionConfig in $actionRule.scheduledActionConfigurations)
{
$notificationTemplate = $null
$notificationTemplateId = $null
$additionalNotifications = $null
$additionalNotificationsList = $null
if($actionConfig.actionType -eq "notification")
{
# Had to change the key of the language string.
# Json in 5.x does not support importing json data with two keys with the same name e.g.
# notification and Notification
# Not a good workaround but -AsHashtable is only available in PowerShell 6.x + and JavaScriptSerializer is not good at encoding
$notificationEnumString = "emailNotification"
}
else
{
$notificationEnumString = $actionConfig.actionType
}
$actionType = Get-LanguageString "ScheduledAction.$($notificationEnumString)"
if($actionConfig.gracePeriodHours -eq 0)
{
$schedule = Get-LanguageString "ScheduledAction.List.immediately"
}
else
{
# Always sets the gracePeriodHours property but always uses days
$schedule = ((Get-LanguageString "ScheduledAction.List.gracePeriodDays") -f ($actionConfig.gracePeriodHours/24))
}
if($actionConfig.actionType -eq "notification")
{
if($actionConfig.notificationTemplateId -ne [Guid]::Empty)
{
$notificationTemplate = Get-LanguageString "ScheduledAction.Notification.selected"
$notificationTemplateId = $actionConfig.notificationTemplateId
}
else
{
$notificationTemplate = Get-LanguageString "ScheduledAction.Notification.noneSelected"
}
if($actionConfig.notificationMessageCCList.Count -gt 0)
{
$additionalNotifications = ((Get-LanguageString "ScheduledAction.Notification.numSelected") -f $actionConfig.notificationMessageCCList.Count)
$additionalNotificationsList = $actionConfig.notificationMessageCCList -join ","
}
else
{
$additionalNotifications = Get-LanguageString "ScheduledAction.Notification.noneSelected"
}
}
$objClone = $actionConfig | ConvertTo-Json -Depth 10 | ConvertFrom-Json
Remove-Property $objClone "Id"
foreach($prop in $objClone.PSObject.Properties)
{
if($prop.Name -like "*@odata*")
{
Remove-Property $objClone $prop.Name
}
}
# ToDo: Resolve MessageTemplateId and EmailCCIds to actual object names
$script:objectComplianceActionData += New-Object PSObject -Property @{
IdStr = ($objClone | ConvertTo-Json -Depth 10 -Compress)
Action = $actionType
Schedule = $schedule
MessageTemplate = $notificationTemplate
MessageTemplateId = $notificationTemplateId
EmailCC = $additionalNotifications
EmailCCIds = $additionalNotificationsList
Category=$category
RawJsonValue=($actionConfig | ConvertTo-Json -Depth 20 -Compress)
}
}
}
}
#endregion
#region Assignments
function Invoke-TranslateAssignments
{
param($obj)
$filtersInfo = $null
$included = @()
$excluded = @()
$tmpAssignments = @()
$groupIds = @()
$filterIds = @()
$category = Get-LanguageString "TableHeaders.assignments"
$groupModeInclude = Get-LanguageString "TableHeaders.includedGroups"
$groupModeExnclude = Get-LanguageString "TableHeaders.excludedGroups"
$noFilter = Get-LanguageString "AssignmentFilters.noFilters"
$filterInclude = Get-LanguageString "SettingDetails.include"
$filterExclude = Get-LanguageString "SettingDetails.exclude"
foreach($assignment in $obj.assignments)
{
if($assignment.target.groupId -and $assignment.target.groupId -notin $groupIds)
{
$groupIds += $assignment.target.groupId
}
if($assignment.target.deviceAndAppManagementAssignmentFilterId -and ($assignment.target.deviceAndAppManagementAssignmentFilterId -notin $filterIds))
{
$filterIds += $assignment.target.deviceAndAppManagementAssignmentFilterId
}
}
$groupInfo = $null
if($groupIds.Count -gt 0)
{
$ht = @{}
$ht.Add("ids", @($groupIds | Unique))
$body = $ht | ConvertTo-Json
$groupInfo = (Invoke-GraphRequest -Url "/directoryObjects/getByIds?`$select=displayName,id" -Content $body -Method "Post").Value
}
if(($null -eq $groupInfo -or ($groupInfo | measure).Count -eq 0) -and $obj."@ObjectFromFile" -eq $true -and $script:migTable)
{
### Get group info from mig table when documenting from file if there's no access to the environment
$groupInfo = $script:migTable.Objects | Where Type -eq "Group"
}
if($filterIds.Count -gt 0)
{
$batchInfo = @{}
$requests = @()
#{"requests":[{"id":"<FilterID>","method":"GET","url":"deviceManagement/assignmentFilters/<FilterID>?$select=displayName"}]}
foreach($filterId in $filterIds)
{
$requests += [PSCustomObject]@{
id = $filterIds
method = "GET"
"url" = "deviceManagement/assignmentFilters/$($filterId)?`$select=displayName"
}
}
$batchInfo = @{"requests"=$requests}
$jsonBody = $batchInfo | ConvertTo-Json
$filtersInfo = Invoke-GraphRequest -Url "/`$batch" -Content $jsonBody -Method "Post"
}
foreach($assignment in $obj.assignments)
{
$groupMode = $null
$groupName = $null
$filterName = $null
$filterMode = $null
if($assignment.Intent)
{
$assignmentType = "mobileAppAssignmentSettings"
}
else
{
$assignmentType = "genericAssignment"
}
if($assignment.target.'@odata.type' -eq "#microsoft.graph.exclusionGroupAssignmentTarget")
{
$groupMode = "exclude"
}
else
{
$groupMode = "include"
}
if($assignment.target.GroupId)
{
$groupName = ($groupInfo | Where id -eq $assignment.target.GroupId).displayName
if(-not $groupName)
{
$groupName = $assignment.target.GroupId
}
}
elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allDevicesAssignmentTarget")
{
$groupName = Get-LanguageString "SettingDetails.allUsers"
}
elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allLicensedUsersAssignmentTarget")
{
$groupName = Get-LanguageString "SettingDetails.allDevices"
}
else
{
$groupName = "unknown" # Should not get here!
}
if(($assignment.target.PSObject.Properties | Where Name -eq "deviceAndAppManagementAssignmentFilterId"))
{
$filterName = $noFilter
$filterMode = $noFilter
if($assignment.target.deviceAndAppManagementAssignmentFilterId -and $filtersInfo.responses)
{
$filtersObj = $filtersInfo.responses | Where Id -eq $assignment.target.deviceAndAppManagementAssignmentFilterId
if($filtersObj.body.displayName)
{
$filterName = $filtersObj.body.displayName
}
if($assignment.target.deviceAndAppManagementAssignmentFilterType -eq "include")
{
$filterMode = $filterInclude
}
else
{
$filterMode = $filterExclude
}
}
}
# ToDo: Add support if Direct or from Policy Set
# Add additional app assignment settings
# Only add settings for Included assignments
$assignmentSettingProps = [ordered]@{}
if($assignmentType -eq "mobileAppAssignmentSettings" -and $groupMode -eq "include" -and $assignment.settings -ne $null)
{
foreach($settingProp in @("useDeviceContext",
"vpnConfigurationId",
"uninstallOnDeviceRemoval",
"isRemovable",
"useDeviceLicensing",
"androidManagedStoreAppTrackIds",
"deliveryOptimizationPriority",
"installTimeSettings",
"notifications",
"restartSettings"))
{
if(($assignment.settings.PSObject.Properties | Where Name -eq $settingProp))
{
if($settingProp -eq "useDeviceLicensing")
{
if($assignment.settings.$settingProp -eq $true)
{
$value = Get-LanguageString "SettingDetails.licenseTypeDevice"
}
else
{
$value = Get-LanguageString "SettingDetails.licenseTypeUser"
}
}
else
{
$value = $assignment.settings.$settingProp
}
$assignmentSettingProps.Add($settingProp, $value)
}
}
}
if($assignmentType -eq "genericAssignment")
{
$assignObj = [PSCustomObject]@{
GroupMode = (?: ($groupMode -eq "include") $groupModeInclude $groupModeExnclude)
Group = $groupName
Type = "GenericAssignment"
Category = (?: ($groupMode -eq "include") $groupModeInclude $groupModeExnclude)
}
if($groupMode -eq "include")
{
$included += $assignObj
if($filterMode -ne $null)
{
$assignObj | Add-Member Noteproperty -Name "Filter" -Value $filterName -Force
$assignObj | Add-Member Noteproperty -Name "FilterMode" -Value $filterMode -Force
}
}
else
{
$excluded += $assignObj
}
}
else
{
$appAssignment = @{
Group = $groupName
GroupMode = Get-LanguageString "AssignmentAction.$($groupMode)"
Category = Get-LanguageString "InstallIntent.$($assignment.Intent)"
RawIntent = $assignment.Intent
RawJsonValue = ($assignment | ConvertTo-Json -Depth 20 -Compress)
}
if($groupMode -eq "include")
{
$appAssignment.Add("Filter", $filterName)
$appAssignment.Add("FilterMode", $filterMode)
}
if($assignmentSettingProps.Count -gt 0)
{
$appAssignment.Add("Settings", $assignmentSettingProps)
}
$tmpAssignments += [PSCustomObject]$appAssignment
}
}
if($included.Count -gt 0)
{
$script:objectAssignments += $included
<#
$script:objectAssignments += [PSCustomObject]@{
GroupMode = Get-LanguageString "TableHeaders.includedGroups"
Groups = ($included -join $script:objectSeparator)
Type = "GenericAssignment"
}
#>
}
if($excluded.Count -gt 0)
{
$script:objectAssignments += $excluded
<#
$script:objectAssignments += [PSCustomObject]@{
GroupMode = Get-LanguageString "TableHeaders.excludedGroups"
Groups = ($excluded -join $script:objectSeparator)
Type = "GenericAssignment"
}
#>
}
if($tmpAssignments.Count -gt 0)
{
# Sort the items in the correct order
foreach($intent in @("required","available","availableWithoutEnrollment","uninstall"))
{
$script:objectAssignments += $tmpAssignments | Where RawIntent -eq $intent
}
}
}
#endregion
#region Applications
function Get-GraphAppType
{
param($obj)
if(-not $script:allAppTypes)
{
$fi = [IO.FileInfo]($global:AppRootFolder + "\Documentation\AppTypes.json")
if(!$fi.Exists)
{
return $false
}
$script:allAppTypes = Get-Content ($global:AppRootFolder + "\Documentation\AppTypes.json") | ConvertFrom-Json
}
foreach($appType in ($script:allAppTypes | Where ODataType -eq $obj.'@OData.Type'))
{
if($appType.Condition)
{
if($obj."$($appType.Condition.Property)" -eq $appType.Condition.Value)
{
return $appType
}
}
else
{
return $appType
}
}
}
#endregion
#region Custom Policy objects - base on json file named after OData.Type
function Invoke-TranslateCustomProfileObject
{
param($obj, $fileName)
$fi = [IO.FileInfo]($global:AppRootFolder + "\Documentation\ObjectInfo\$($fileName).json")
if(!$fi.Exists)
{
return $false
}
$jsonObj = Get-Content ($global:AppRootFolder + "\Documentation\ObjectInfo\$($fileName).json") | ConvertFrom-Json
$platformType = Get-ObjectPlatformFromType $obj
Add-BasicDefaultValues $obj $objectType
Add-CustomProfileProperties $obj
Invoke-TranslateSection $obj $jsonObj
Add-BasicAdditionalValues $obj $objectType
return $true
}
#endregion
function Add-CustomSettingObject
{
param($settingsObj)
$script:objectSettingsData += $settingsObj
}
#region Initiate documentation
function Show-DocumentationForm
{
param(
[Parameter(Mandatory=$true,ParameterSetName = "Objects")]
$objects,
[Parameter(Mandatory=$true,ParameterSetName = "ObjectTypes")]
$objectTypes,
[Switch]
[Parameter(Mandatory=$false,ParameterSetName = "Objects")]
$SelectedDocuments,
[Switch]
$ShowFolderSource
)
$objectList = @()
if($objects)
{
if($SelectedDocuments -eq $true)
{
$objectList += $objects
}
else
{
$objects | ForEach-Object {
$item = [PSCustomObject]@{
IsSelected = $true
Title = (Get-GraphObjectName $_.Object $_.ObjectType)
Object = $_.Object
ObjectType = $_.ObjectType
}
$objectList += $item
}
}
$sourceType = "Objects"
}
elseif($objectTypes)
{
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
if(-not $groupName)
{
Write-Log "Group id $groupId could not be translated" 2
$groupName = $groupId
}
$item = [PSCustomObject]@{
IsSelected = $true
Title = $groupName
GroupId = $groupId
}
$objectList += $item
}
$objectList = $objectList | Sort -Property Title
$sourceType = "ObjectTypes"
}
else
{
return
}
if($objectList.Count -eq 0) { Write-Log "No objects found/selected!";return }
$ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($objectList))
$script:docForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\DocumentationForm.xaml") -AddVariables
if(-not $script:docForm) { return }
$global:grdDocumentObjects.Tag = $sourceType
if($sourceType -eq "Objects")
{
$global:btnClearDocumentationList.Visibility = ?: ($SelectedDocuments -eq $true) "Visible" "Collapsed"
$global:btnAddToDocumentationList.Visibility = ?: ($SelectedDocuments -ne $true) "Visible" "Collapsed"
}
else
{
$global:btnClearDocumentationList.Visibility = "Collapsed"
$global:btnAddToDocumentationList.Visibility = "Collapsed"
}
if($ShowFolderSource -ne $true)
{
$global:grdDocumentFromFolder.Visibility = "Collapsed"
$global:spDocumentFromFolder.Visibility = "Collapsed"
}
else
{
Add-XamlEvent $script:docForm "browseDocumentFromFolder" "add_click" {
$folder = Get-Folder (Get-XamlProperty $script:docForm "txtDocumentFromFolder" "Text") "Select root folder for export files"
if($folder)
{
Set-XamlProperty $script:docForm "txtDocumentFromFolder" "Text" $folder
}
}
}
$column = Get-GridCheckboxColumn "IsSelected"
$global:grdDocumentObjects.Columns.Add($column)
$column.Header.IsChecked = $true # All items are checked by default
$column.Header.add_Click({
foreach($item in $global:grdDocumentObjects.ItemsSource)
{
$item.IsSelected = $this.IsChecked
}
$global:grdDocumentObjects.Items.Refresh()
}
)
# Add title column
$binding = [System.Windows.Data.Binding]::new("Title")
$column = [System.Windows.Controls.DataGridTextColumn]::new()
$column.Header = "Title"
$column.IsReadOnly = $true
$column.Binding = $binding
$global:grdDocumentObjects.Columns.Add($column)
if($SelectedDocuments -eq $true)
{
$binding = [System.Windows.Data.Binding]::new("ObjectType.Id")
$column = [System.Windows.Controls.DataGridTextColumn]::new()
$column.Header = "Type"
$column.IsReadOnly = $true
$column.Binding = $binding
$global:grdDocumentObjects.Columns.Add($column)
}
$global:grdDocumentObjects.ItemsSource = [System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList)
$global:cbDocumentationType.ItemsSource = $global:documentationOutput
$global:cbDocumentationType.SelectedValue = (Get-Setting "Documentation" "OutputType" "none")
if(-not $script:Languages)
{
$script:Languages = Get-Content ($global:AppRootFolder + "\Documentation\Languages.json") -Encoding UTF8 | ConvertFrom-Json
}
if($script:Languages)
{
$global:cbDocumentationLanguage.ItemsSource = $script:Languages
$global:cbDocumentationLanguage.SelectedValue = (Get-Setting "Documentation" "Language" "en")
}
$global:cbDocumentationPropertySeparator.ItemsSource = @(",",";","-","|")
try
{
$global:cbDocumentationPropertySeparator.SelectedIndex = $global:cbDocumentationPropertySeparator.ItemsSource.IndexOf((Get-Setting "Documentation" "PropertySeparator" ";"))
}
catch {}
$objectSeparator = "[ { Name: `"New line`",Value: `"$([System.Environment]::NewLine)`" }, {Name: `";`",Value: `";`" }, {Name: `"|`",Value: `"|`" }]" | ConvertFrom-Json
$global:cbDocumentationObjectSeparator.ItemsSource = $objectSeparator
$global:cbDocumentationObjectSeparator.SelectedValue = (Get-Setting "Documentation" "ObjectSeparator" ([System.Environment]::NewLine)) #"$([System.Environment]::NewLine)")
$global:chkSetUnconfiguredValue.IsChecked = ((Get-Setting "Documentation" "SetUnconfiguredValue" "true") -ne "false")
$global:chkSetDefaultValue.IsChecked = ((Get-Setting "Documentation" "SetDefaultValue" "false") -ne "false")
$notConfiguredItems = "[ { Name: `"Not configured (Localized)`",Value: `"notConfigured`" }, { Name: `"Empty`",Value: `"empty`" }, { Name: `"Don't change`",Value: `"asis`" }]" | ConvertFrom-Json
$global:cbNotConifugredText.ItemsSource = $notConfiguredItems
$global:cbNotConifugredText.SelectedValue = (Get-Setting "Documentation" "NotConfiguredText" "")
$global:chkSkipNotConfigured.IsChecked = ((Get-Setting "Documentation" "SkipNotConfigured" "false") -ne "false")
$global:chkSkipDefaultValues.IsChecked = ((Get-Setting "Documentation" "SkipDefaultValues" "false") -ne "false")
$global:chkSkipDisabled.IsChecked = ((Get-Setting "Documentation" "SkipDisabled" "true") -ne "false")
Add-XamlEvent $script:docForm "btnClose" "add_click" {
$script:docForm = $null
Show-ModalObject
}
Add-XamlEvent $script:docForm "btnAddToDocumentationList" "add_click" {
Add-DocumentationObjects ($global:grdDocumentObjects.ItemsSource | Where IsSelected -eq $true)
}
Add-XamlEvent $script:docForm "btnClearDocumentationList" "add_click" {
if([System.Windows.MessageBox]::Show("Are you sure you want to clear the items in the list?", "Documentation", "YesNo", "Question") -eq "Yes")
{
$global:grdDocumentObjects.ItemsSource = $null
$global:btnStartDocumentation.IsEnabled = $false
}
}
Add-XamlEvent $script:docForm "btnStartDocumentation" "add_click" {
$txtDocumentationRawData.Text = ""
$loadExportedInfo = $true
$script:migTable = $null
$script:scopeTags = $null
$diSource = $nul
$global:intentCategories = $null
$global:catRecommendedSettings = $null
$global:intentCategoryDefs = $null
$global:cfgCategories = $null
$script:DocumentationLanguage = ?? $global:cbDocumentationLanguage.SelectedValue "en"
$script:objectSeparator = ?? $global:cbDocumentationObjectSeparator.SelectedValue ([System.Environment]::NewLine)
$script:propertySeparator = ?? $global:cbDocumentationPropertySeparator.SelectedValue ","
Save-Setting "Documentation" "OutputType" $global:cbDocumentationType.SelectedValue
Save-Setting "Documentation" "Language" $script:DocumentationLanguage
Save-Setting "Documentation" "ObjectSeparator" $script:objectSeparator
Save-Setting "Documentation" "PropertySeparator" $script:propertySeparator
Save-Setting "Documentation" "SetUnconfiguredValue" $global:chkSetUnconfiguredValue.IsChecked
Save-Setting "Documentation" "SetDefaultValue" $global:chkSetDefaultValue.IsChecked
Save-Setting "Documentation" "SkipNotConfigured" $global:chkSkipNotConfigured.IsChecked
Save-Setting "Documentation" "SkipDefaultValues" $global:chkSkipDefaultValues.IsChecked
Save-Setting "Documentation" "SkipDisabled" $global:chkSkipDisabled.IsChecked
Save-Setting "Documentation" "NotConfiguredText" $global:cbNotConifugredText.SelectedValue
Get-CustomIgnoredCategories $obj
if($global:grdDocumentObjects.Tag -eq "Objects")
{
$sourceList = @()
$groupIds = $global:grdDocumentObjects.ItemsSource.ObjectType.GroupId | Select -Unique | Sort GroupId
foreach($groupId in $groupIds)
{
$groupSourceList = @()
$curObjectType = $null
foreach($tmpObj in ($global:grdDocumentObjects.ItemsSource | Where { $_.IsSelected -eq $true -and $_.ObjectType.GroupId -eq $groupId } ))
{
$groupSourceList += $tmpObj
$curObjectType = $tmpObj.ObjectType
}
if($groupSourceList.Count -eq 0) { contnue }
if($curObjectType -eq "EndpointSecurity")
{
$catName = Get-IntentCategory $tmpObj.Category
$tmpObj | Add-Member Noteproperty -Name "CategoryName" -Value $catName -Force
$sortProps = @("CategoryName","displayName")
}
else
{
$sortProps = @((?? $objectType.NameProperty "displayName"))
}
$sourceList += $groupSourceList | Sort-Object -Property $sortProps
}
}
elseif($global:grdDocumentObjects.Tag -eq "ObjectTypes")
{
$fromExportFolder = $false
if($global:txtDocumentFromFolder.Text)
{
$diSource = [IO.DirectoryInfo]$global:txtDocumentFromFolder.Text
if($diSource.Exists -eq $false)
{
[System.Windows.MessageBox]::Show("Source folder not found:`n`n$($diSource.FullName)", "Documentation", "OK", "Error")
Write-Status ""
return
}
$fromExportFolder = $true
}
$sourceList = @()
foreach($objGroup in ($global:grdDocumentObjects.ItemsSource | Where IsSelected -eq $true))
{
$groupSourceList = @()
foreach($objectType in ($global:currentViewObject.ViewItems | Where GroupId -eq $objGroup.GroupId))
{
Write-Status "Get $($objectType.Title) objects"
if($fromExportFolder -eq $false)
{
$graphObjects = @(Get-GraphObjects -property $objectType.ViewProperties -objectType $objectType)
if($objectType.PostListCommand)
{
$graphObjects = & $objectType.PostListCommand $graphObjects $objectType
}
}
else
{
$objectPath = [IO.Path]::Combine($diSource.FullName,$objectType.ID)
if([IO.Directory]::Exists($objectPath) -eq $false)
{
Write-Log "Object path for $($objectType.Title) ($($objectType.ID)) not found. Skipping object type" 2
continue
}
$graphObjects = Get-GraphFileObjects $objectPath -ObjectType $objectType
$graphObjects | ForEach-Object { $_.Object | Add-Member Noteproperty -Name "@ObjectFromFile" -Value $true -Force }
}
$groupSourceList += $graphObjects
}
if($objGroup.GroupId -eq "EndpointSecurity")
{
foreach($tmpObj in $groupSourceList)
{
$catName = Get-IntentCategory $tmpObj.Category
$tmpObj | Add-Member Noteproperty -Name "CategoryName" -Value $catName -Force
}
$sortProps = @("CategoryName","displayName")
}
else
{
$sortProps = @((?? $objectType.NameProperty "displayName"))
}
$sourceList += $groupSourceList | Sort-Object -Property $sortProps
}
}
else
{
return
}
if($fromExportFolder -eq $true -and $diSource -and $loadExportedInfo -eq $true)
{
$loadExportedInfo = $false
$migFileName = [IO.Path]::Combine($diSource.FullName,"MigrationTable.json")
if([IO.File]::Exists($migFileName) -eq $false)
{
Write-Log "MigrationTable not found. Groups will be documented with GroupId" 2
}
else
{
Write-Log "Load Migration table from $migFileName"
$script:migTable = ConvertFrom-Json (Get-Content $migFileName -Raw)
}
$scopeTagObjectType = $global:currentViewObject.ViewItems | Where Id -eq "ScopeTags"
if($scopeTagObjectType)
{
$scopePath = [IO.Path]::Combine($diSource.FullName,$scopeTagObjectType.Id)
if([IO.Directory]::Exists($scopePath) -eq $false)
{
Write-Log "Object path for Scope (Tags) ($($scopePath)) not found" 2
}
else
{
$scopeTagObjects = Get-GraphFileObjects $scopePath -ObjectType $scopeTagObjectType
$script:scopeTags = @(($scopeTagObjects | Select Object))
}
}
}
if($global:cbDocumentationType.SelectedItem.PreProcess)
{
Write-Status "Run PreProcess for $($global:cbDocumentationType.SelectedItem.Name)"
& $global:cbDocumentationType.SelectedItem.PreProcess
}
$tmpCurObjectType = $null
$tmpCurObjectGroup = $null
$allObjectTypeObjects = @()
foreach($tmpObj in ($sourceList))
{
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
}
}
if($tmpObj.Object."@ObjectFromFile" -eq $true)
{
$obj = $tmpObj
}
else
{
$obj = Get-GraphObject $tmpObj.Object $tmpObj.ObjectType
}
if($obj)
{
$documentedObj = Get-ObjectDocumentation $obj
if($documentedObj.ErrorText)
{
$txtDocumentationRawData.Text += "#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
$txtDocumentationRawData.Text += "`n#`n# Object: $((Get-GraphObjectName $obj.Object $obj.ObjectType))"
$txtDocumentationRawData.Text += "`n# Type: $($obj.Object.'@OData.Type')"
$txtDocumentationRawData.Text += "`n#`n# Object not documented. Error:"
$txtDocumentationRawData.Text += "`n# $(($documentedObj.ErrorText -replace "`n","`n# "))"
$txtDocumentationRawData.Text += "`n#`n#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
continue
}
if($global:cbDocumentationType.SelectedItem.CustomProcess)
{
# The provider takes care of all the processing
Write-Status "Run CustomProcess for $($global:cbDocumentationType.SelectedItem.Name)"
$ret = & $global:cbDocumentationType.SelectedItem.CustomProcess $obj $documentedObj
if($ret -is [boolean] -and $ret -eq $true) { continue }
}
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)"
$ret = & $global:cbDocumentationType.SelectedItem.NewObjectType $obj $documentedObj
if($ret -is [boolean] -and $ret -eq $true) { continue }
}
$tmpCurObjectType = $obj.ObjectType.Id
$allObjectTypeObjects = @()
}
if($documentedObj)
{
Add-RawDataInfo $obj.Object $obj.ObjectType
$updateNotConfigured = $true
$notConfiguredLoc = Get-LanguageString "BooleanActions.notConfigured"
$notConfiguredText = ""
if($global:cbNotConifugredText.SelectedValue -eq "notConfigured")
{
$notConfiguredText = $notConfiguredLoc
}
elseif($global:cbNotConifugredText.SelectedValue -eq "asis")
{
$updateNotConfigured = $false
}
if($global:cbDocumentationType.SelectedItem.Process)
{
Write-Status "Process $((Get-GraphObjectName $tmpObj.Object $tmpObj.ObjectType)) ($($obj.ObjectType.Title)) - $($global:cbDocumentationType.SelectedItem.Name)"
$filteredSettings = @()
foreach($item in $documentedObj.Settings)
{
if(-not ($item.PSObject.Properties | Where Name -eq "RawValue") -or $documentedObj.UpdateFilteredObject -eq $false)
{
$filteredSettings = $documentedObj.Settings
break
}
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))
{
# Skip unconfigured items e.g. boolean with a value but Not Configured
continue
}
if($global:chkSkipDefaultValues.IsChecked -and (($item.DefaultValue -and $item.RawValue -eq $item.DefaultValue) -or ($item.UnconfiguredValue -and $item.RawValue -eq $item.UnconfiguredValue)))
{
# Skip items that is using default or unconfiguered values
continue
}
if($global:chkSkipDisabled.IsChecked -and ($item.Enabled -is [Boolean] -and ($item.Enabled -eq $false)))
{
# Skip Disabled items
continue
}
elseif($item.EntityKey -and ($item.Enabled -is [Boolean] -and ($item.Enabled -eq $false)))
{
if(($documentedObj.Settings | Where { $_.EntityKey -eq $item.EntityKey -and $_.Enabled -eq $true }))
{
# Skip a disabled item if there is another item with the same property that is enabled
continue
}
}
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
}
if($global:chkSkipNotConfigured.IsChecked -and $item.Value -eq $notConfiguredLoc)
{
# Skip unconfigured items based on value e.g. value = Not Configured
Write-Log "Skipping property $($itenm.Name) based on '$($notConfiguredLoc)' string value" 2
continue
}
$filteredSettings += $item
}
$documentedObj | Add-Member Noteproperty -Name "FilteredSettings" -Value $filteredSettings -Force
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)"
& $global:cbDocumentationType.SelectedItem.PostProcess
}
Write-Status ""
}
Add-XamlEvent $script:docForm "btnCopyBasic" "add_click" {
if($script:objectBasicInfo.Count -gt 0)
{
$script:objectBasicInfo | Select -Property Name,Value | ConvertTo-Csv -NoTypeInformation | Set-Clipboard
}
}
Add-XamlEvent $script:docForm "btnCopySettings" "add_click" {
if($script:objectSettingsData.Count -gt 0)
{
$script:objectSettingsData | Select -Property $script:settingsProperties | ConvertTo-Csv -NoTypeInformation | Set-Clipboard
}
}
Add-XamlEvent $script:docForm "btnCopyAll" "add_click" {
$tmpArr = @()
if($script:objectBasicInfo.Count -gt 0)
{
$tmpArr += $script:objectBasicInfo | Select -Property Name,Value | ConvertTo-Csv -NoTypeInformation
}
if($script:objectSettingsData.Count -gt 0)
{
$tmpArr += $script:objectSettingsData | Select -Property $script:settingsProperties | ConvertTo-Csv -NoTypeInformation
}
if($script:applicabilityRules.Count -gt 0)
{
$tmpArr += $script:applicabilityRules | Select -Property Rule,Property,Value,Category | ConvertTo-Csv -NoTypeInformation
}
if($script:objectComplianceActionData.Count -gt 0)
{
$tmpArr += $script:objectComplianceActionData | Select Action,Schedule,MessageTemplate,EmailCC,Category | ConvertTo-Csv -NoTypeInformation
}
$tmpArr | Set-Clipboard
}
$global:cbDocumentationType.Add_SelectionChanged({
Set-OutputOptionsTabStatus $this
})
Set-OutputOptionsTabStatus $global:cbDocumentationType
Show-ModalForm "Intune Documentation" $script:docForm -HideButtons
}
function Set-OutputOptionsTabStatus
{
param($control)
$global:tabOutputSettings.Visibility = (?: ($control.SelectedItem.Value -eq "none") "Collapsed" "Visible")
$global:lblCustomOptions.Content = ?: $control.SelectedItem.OutputOptions ($control.SelectedItem.Name) ""
$global:ccOutputCustomOptions.Content = $control.SelectedItem.OutputOptions
if($control.SelectedItem.Activate)
{
& $control.SelectedItem.Activate
}
}
function Invoke-DocumentObjectTypes
{
Show-DocumentationForm -objectTypes $global:currentViewObject.ViewItems -ShowFolderSource
}
function Invoke-DocumentSelectedObjects
{
if(-not $script:selectedObjects -or $script:selectedObjects.Count -eq 0)
{
[System.Windows.MessageBox]::Show("No objects added for documentation", "Documentation", "OK", "Info")
return
}
Show-DocumentationForm -objects $script:selectedObjects -SelectedDocuments
}
function Add-DocumentationObjects
{
param($objects)
$itemCount = ($objects | measure).Count
if($itemCount -eq 0) { return }
if(-not $script:selectedObjects)
{
$script:selectedObjects = @()
}
$script:selectedObjects += $objects
[System.Windows.MessageBox]::Show("$itemCount object(s) added to the documentation list", "Documentation", "OK", "Info")
}
function Clear-DocumentationObjects
{
$script:selectedObjects = @()
}
#endregion
#region CSV Options
function Add-CSVOptionsControl
{
$script:csvForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\DocumentationCSVOptions.xaml") -AddVariables
$global:cbCSVDocumentationProperties.ItemsSource = ("[ { Name: `"Simple (Name and Value)`",Value: `"simple`" }, { Name: `"Extended`",Value: `"extended`" }, { Name: `"Custom`",Value: `"custom`" }]" | ConvertFrom-Json)
$global:cbCSVDocumentationProperties.SelectedValue = (Get-Setting "Documentation" "CSVExportProperties" "simple")
$global:txtCSVCustomProperties.Text = (Get-Setting "Documentation" "CSVCustomDisplayProperties" "Name,Value,Category")
$global:spCSVCustomProperties.Visibility = (?: ($global:cbCSVDocumentationProperties.SelectedValue -ne "custom") "Collapsed" "Visible")
$global:txtCSVCustomProperties.Visibility = (?: ($global:cbCSVDocumentationProperties.SelectedValue -ne "custom") "Collapsed" "Visible")
$global:cbCSVDelimiter.ItemsSource = @("", ",",";","-","|")
try
{
$global:cbCSVDelimiter.SelectedIndex = $global:cbCSVDelimiter.ItemsSource.IndexOf((Get-Setting "Documentation" "CSVDelimiter"))
}
catch {}
Add-XamlEvent $script:csvForm "browseCSVDocumentationPath" "add_click" {
$folder = Get-Folder (Get-XamlProperty $script:csvForm "txtCSVDocumentationPath" "Text") "Select root folder for export"
if($folder)
{
Set-XamlProperty $script:csvForm "txtCSVDocumentationPath" "Text" $folder
}
}
Add-XamlEvent $script:csvForm "cbCSVDocumentationProperties" "add_selectionChanged" {
$global:spCSVCustomProperties.Visibility = (?: ($this.SelectedValue -ne "custom") "Collapsed" "Visible")
$global:txtCSVCustomProperties.Visibility = (?: ($this.SelectedValue -ne "custom") "Collapsed" "Visible")
}
$script:csvForm
}
function Invoke-CSVActivate
{
$global:txtCSVDocumentationPath.Text = (?? (Get-Setting "" "LastUsedRoot") (Get-SettingValue "RootFolder"))
$global:chkCSVAddObjectType.IsChecked = (Get-SettingValue "AddObjectType")
$global:chkCSVAddCompanyName.IsChecked = (Get-SettingValue "AddCompanyName")
}
function Invoke-CSVPreProcessItems
{
Save-Setting "Documentation" "CSVExportProperties" $global:cbCSVDocumentationProperties.SelectedValue
Save-Setting "Documentation" "CSVCustomDisplayProperties" $global:txtCSVCustomProperties.Text
Save-Setting "Documentation" "CSVDelimiter" $global:cbCSVDelimiter.Text
}
function Invoke-CSVProcessItem
{
param($obj, $objectType, $documentedObj)
if(!$documentedObj -or !$obj -or !$objectType) { return }
$folder = Get-GraphObjectFolder $objectType (Get-XamlProperty $script:csvForm "txtCSVDocumentationPath" "Text") (Get-XamlProperty $script:csvForm "chkCSVAddObjectType" "IsChecked") (Get-XamlProperty $script:csvForm "chkCSVAddCompanyName" "IsChecked")
try
{
if([IO.Directory]::Exists($folder) -eq $false)
{
[IO.Directory]::CreateDirectory($folder)
}
$objName = Get-GraphObjectName $obj $objectType
#BasicInfo
#Settings
#ComplianceActions
#Assignments
#DisplayProperties
$itemsToExport = @()
$params = @{}
if($global:cbCSVDelimiter.Text)
{
$params.Add('Delimiter',$global:cbCSVDelimiter.Text)
}
if(($global:cbCSVDocumentationProperties.SelectedValue -eq 'extended' -and $documentedObj.DisplayProperties) -or
($global:cbCSVDocumentationProperties.SelectedValue -eq 'custom' -and $global:txtCSVCustomProperties.Text))
{
if(($documentedObj.BasicInfo | measure).Count -gt 0)
{
$itemsToExport += ""
$itemsToExport += "# Basic info"
$itemsToExport += ""
$itemsToExport += $documentedObj.BasicInfo | ConvertTo-Csv -NoTypeInformation
}
if(($documentedObj.FilteredSettings | measure).Count -gt 0)
{
$itemsToExport += ""
$itemsToExport += "# Settings"
$itemsToExport += ""
if($global:cbCSVDocumentationProperties.SelectedValue -eq 'extended')
{
$displayProperties = $documentedObj.DisplayProperties
}
else
{
$displayProperties = $global:txtCSVCustomProperties.Text.Split(",")
}
$itemsToExport += $documentedObj.FilteredSettings | Select $displayProperties | ConvertTo-Csv -NoTypeInformation
}
if(($documentedObj.ApplicabilityRules | measure).Count -gt 0)
{
$itemsToExport += ""
$itemsToExport += "# Applicability Rules"
$itemsToExport += ""
$itemsToExport += $script:applicabilityRules | Select Rule,Property,Value,Category | ConvertTo-Csv -NoTypeInformation
}
if(($documentedObj.ComplianceActions | measure).Count -gt 0)
{
$itemsToExport += ""
$itemsToExport += "# Compliance Actions"
$itemsToExport += ""
$itemsToExport += $documentedObj.ComplianceActions | Select Action,Schedule,MessageTemplate,EmailCC,Category | ConvertTo-Csv -NoTypeInformation
}
if(($documentedObj.Assignments | measure).Count -gt 0)
{
if($documentedObj.Assignments[0].RawIntent)
{
$properties = @("GroupMode","Group","Category","SubCategory")
}
else
{
$properties = @("GroupMode","Groups","Category")
}
$itemsToExport += ""
$itemsToExport += "# Assignments"
$itemsToExport += ""
$itemsToExport += $documentedObj.Assignments | Select $properties | ConvertTo-Csv -NoTypeInformation @params
}
}
else
{
$itemsToExport += $documentedObj.BasicInfo
$itemsToExport += $documentedObj.FilteredSettings
$itemsToExport = $itemsToExport | Select Name,Value | ConvertTo-Csv -NoTypeInformation @params
}
$fileName = $folder + "\$((Remove-InvalidFileNameChars $objName)).csv"
Write-Log "Save documentation to $fileName"
$itemsToExport | Out-File -LiteralPath $fileName -Encoding UTF8 -Force
}
catch
{
Write-LogError "Failed to save CSV file $(($folder + "\$($objName).csv"))" $_.Exception
}
}
#endregion