IntuneManagement 4.0.0-beta1

This commit is contained in:
Mikael Karlsson
2026-09-23 19:13:09 +10:00
commit 7869619510
892 changed files with 577109 additions and 0 deletions
@@ -0,0 +1,476 @@
# HTML output provider.
#
# Consumes the per-object documentation result (see DocumentationOutputJson.ps1
# header for the field list). Renders a single HTML document (or one file per
# object) with CSS from Internal/Documentation/Assets/DefaultHTMLStyle.css.
#
# Differences vs old DocumentationHTML.psm1:
# - Drops V1 NewObjectGroup/NewObjectType hooks (V2 string-arg is registered)
# - Drops extended/custom property selectors that read $global:txt*/$global:cb*
# UI controls. Always uses DefaultDocumentationProperties or ('Name','Value').
# Phase 2 [DocumentationContext] will reintroduce these via $ctx.Options.
# - Uses the typed-object API: $PolicyObject.Name + $PolicyObject.PolicyType.Title
# - Invoke-HTMLProcessAllObjects (ScopeTags consolidated table) is stubbed
# because Get-TableObjects doesn't exist yet — wire up in phase 2.
function Invoke-InitializeHTMLOutput {
Add-DocumentationOutputProvider ([PSCustomObject]@{
Name = "HTML"
Value = "html"
# Path metadata for the bulk-doc UI: which option key stores the
# primary output path, and whether that path is a folder or a file.
# File-picker button wiring for the UI is declared separately in
# UI/<backend>/ClassExtensions/DocumentationOutputHTMLUIExtension.ps1
# to keep XAML control names + toolkit-specific filter strings out
# of this pure-logic file.
PrimaryPathOption = "HTMLDocumentName"
PathIsFolder = $false
PreProcess = { Invoke-HTMLPreProcessItems @args }
NewObjectGroup = { Invoke-HTMLNewObjectGroup @args }
NewObjectType = { Invoke-HTMLNewObjectType @args }
Process = { Invoke-HTMLProcessItem @args }
PostProcess = { Invoke-HTMLPostProcessItems @args }
ProcessAllObjects = { Invoke-HTMLProcessAllObjects @args }
})
}
function Invoke-HTMLPreProcessItems {
$script:sectionAnchors = @()
$script:totAnchors = @()
$script:htmlStrings = $null
$script:currentItemFileName = $null
$defaultCSSFile = [IO.Path]::Combine($script:AppRootFolder, "Internal", "Documentation", "Assets", "DefaultHTMLStyle.css")
$htmlCssFile = Get-DocumentationOutputOption html "HTMLCSSFile" $defaultCSSFile
if (-not $htmlCssFile) {
Write-Log "CSS file not specified. Using default" 2
$htmlCssFile = $defaultCSSFile
}
elseif (-not [IO.File]::Exists($htmlCssFile)) {
Write-Log "CSS file $htmlCssFile not found. Using default" 2
$htmlCssFile = $defaultCSSFile
}
if ([IO.File]::Exists($htmlCssFile)) {
Write-Log "Using CSS file $htmlCssFile"
$script:cssStyle = ([IO.File]::ReadAllText($htmlCssFile)) + [Environment]::NewLine
}
else {
Write-Log "CSS file $htmlCssFile not found. No styles applied" 2
$script:cssStyle = ""
}
$fileName = Get-DocumentationOutputOption html "HTMLDocumentName" ""
if (-not $fileName) { $fileName = "%MyDocuments%\%Organization%-%Date%.html" }
$fileName = Expand-FileName $fileName
$script:outFile = $fileName
$script:documentPath = [IO.Path]::GetDirectoryName($fileName)
$script:outputType = Get-DocumentationOutputOption html "HTMLDocumentFileType" "Full"
if ($script:outputType -eq "Object") {
Write-Log "Document one file for each object + index file"
}
else {
Write-Log "Document one single file for all objects"
$script:outputType = "Full"
$script:htmlStrings = [System.Text.StringBuilder]::new()
}
}
function Invoke-HTMLPostProcessItems {
$userName = $null
$mail = ""
$me = Get-CurrentUser
if ($me) {
if ($me.givenName -and $me.surname) {
$userName = "$($me.givenName) $($me.surname)"
}
else {
$userName = $me.displayName
}
if ($me.mail) { $mail = " ($($me.mail))" }
}
$orgName = Get-CurrentOrganizationName
$title = Get-DocumentationOutputOption html "HTMLTitleProperty" "Intune documentation"
if (-not $title) { $title = "Intune documentation" }
$htmlContent = [System.Text.StringBuilder]::new()
[void]$htmlContent.AppendLine("<HTML>")
[void]$htmlContent.AppendLine($script:cssStyle)
[void]$htmlContent.AppendLine("<H1 class='header-level1'>$title</H1>")
if (-not ((Get-DocumentationOption "SkipDocumentInfo" $false) -eq $true)) {
if ($orgName) { [void]$htmlContent.AppendLine("Organization: $orgName<br />") }
if ($userName) { [void]$htmlContent.AppendLine("Generated by: $userName$mail<br />") }
[void]$htmlContent.AppendLine("Generated: $((Get-Date).ToShortDateString()) $((Get-Date).ToLongTimeString())<br />")
}
if ($script:sectionAnchors.Count -gt 0) {
[void]$htmlContent.AppendLine("<br />")
[void]$htmlContent.AppendLine("<H2 class='header-level2'>Table of Contents</H2>")
}
$tocMaxLevel = 4
foreach ($header in $script:sectionAnchors) {
if ($tocMaxLevel -gt 0 -and $header.Level -gt $tocMaxLevel) { continue }
[void]$htmlContent.AppendLine("<a href='$($header.FileName)#$($header.Anchor)' class='anchor-style anchor-level$($header.Level)'>$($header.Name)</a><br />")
}
if ($script:sectionAnchors.Count -gt 0) { [void]$htmlContent.AppendLine("<br />") }
$htmlText = $htmlContent.ToString()
if ($script:outputType -eq "Full" -and $script:htmlStrings) {
$htmlText += $script:htmlStrings.ToString()
}
$htmlText += "</HTML>"
Save-DocumentationFile $htmlText $script:outFile -OpenFile:((Get-DocumentationOutputOption html "HTMLOpenFile" $true) -eq $true)
}
function Invoke-HTMLNewObjectGroup {
param($groupId)
$script:objectHeaderLevel = 2
Add-HTMLHeader (Get-DocObjectTypeString $groupId)
}
function Invoke-HTMLNewObjectType {
param($objectTypeName)
$script:objectHeaderLevel = 3
Add-HTMLHeader $objectTypeName
$script:objectHeaderLevel = 4
}
function Invoke-HTMLProcessAllObjects {
param($documentationInfo)
# ScopeTags consolidated table is deferred — Get-TableObjects helper lands
# in phase 2 alongside the engine. For now this is a no-op.
}
function Invoke-HTMLProcessItem {
param($PolicyObject, $documentedObj)
if (-not $documentedObj -or -not $PolicyObject) { return }
# A documented object may ask to be titled by something other than its display
# name (see Get-DocumentationDisplayName). Headings and captions follow it; the
# file name below deliberately does not.
$objName = Get-DocumentationDisplayName $PolicyObject $documentedObj
$script:docDisplayName = $objName
$typeTitle = $PolicyObject.PolicyType.Title
if ($script:outputType -eq "Object") {
$script:totAnchors = @()
$script:htmlStrings = [System.Text.StringBuilder]::new()
$script:currentItemFileName = Get-HTMLObjectFileName $PolicyObject
}
Add-HTMLHeader $objName
[void]$script:htmlStrings.AppendLine("<br />")
try {
foreach ($tableType in @("BasicInfo","FilteredSettings")) {
if ($tableType -eq "BasicInfo") {
$properties = @("Name","Value")
$lngId = "SettingDetails.basics"
}
else {
if ($documentedObj.DefaultDocumentationProperties) {
$properties = $documentedObj.DefaultDocumentationProperties
}
else {
$properties = @("Name","Value")
}
$lngId = "TableHeaders.settings"
}
# Custom tables with a negative Order belong ABOVE the settings
# table: the portal shows a MAM app config's "Settings catalog"
# blade above its "Settings" blade.
if ($tableType -eq "FilteredSettings") {
foreach ($customTable in ($documentedObj.CustomTables | Where-Object { $_.Order -lt 0 } | Sort-Object -Property Order)) {
Add-HTMLTableItems $PolicyObject $typeTitle $customTable.Values $customTable.Columns $customTable.LanguageId -AddCategories -AddSubcategories
}
}
if (($documentedObj.$tableType | Measure-Object).Count -gt 0) {
Add-HTMLTableItems $PolicyObject $typeTitle $documentedObj.$tableType $properties $lngId -AddCategories -AddSubcategories
}
}
if (($documentedObj.ComplianceActions | Measure-Object).Count -gt 0) {
Add-HTMLTableItems $PolicyObject $typeTitle $documentedObj.ComplianceActions @("Action","Schedule","MessageTemplate","EmailCC") "Category.complianceActionsLabel"
}
if (($documentedObj.ApplicabilityRules | Measure-Object).Count -gt 0) {
Add-HTMLTableItems $PolicyObject $typeTitle $documentedObj.ApplicabilityRules @("Rule","Property","Value") "SettingDetails.applicabilityRules"
}
Add-HTMLObjectScripts $documentedObj
# Negative Order already rendered above the settings table.
foreach ($customTable in ($documentedObj.CustomTables | Where-Object { $_.Order -ge 0 } | Sort-Object -Property Order)) {
Add-HTMLTableItems $PolicyObject $typeTitle $customTable.Values $customTable.Columns $customTable.LanguageId -AddCategories -AddSubcategories
}
if (($documentedObj.Assignments | Measure-Object).Count -gt 0) {
if ($documentedObj.Assignments[0].RawIntent) {
$properties = @("GroupMode","Group","Filter","FilterMode")
$settingsObj = $documentedObj.Assignments | Where-Object { $_.Settings -ne $null } | Select-Object -First 1
if ($settingsObj) {
foreach ($objProp in $settingsObj.Settings.Keys) {
if ($objProp -in $properties) { continue }
if ($objProp -in @("Category","RawIntent")) { continue }
$properties += "Settings.$objProp"
}
}
}
else {
$hasFilter = $false
foreach ($a in $documentedObj.Assignments) {
if ($a.PSObject.Properties.Name -contains "FilterMode") { $hasFilter = $true; break }
}
$properties = @("Group")
if ($hasFilter) { $properties += @("Filter","FilterMode") }
}
Add-HTMLTableItems $PolicyObject $typeTitle $documentedObj.Assignments $properties "TableHeaders.assignments" -AddCategories
}
}
catch {
Write-LogError "Failed to process object $objName" $_.Exception
}
if ($script:outputType -eq "Object") {
$perObject = [System.Text.StringBuilder]::new()
[void]$perObject.AppendLine("<HTML>")
[void]$perObject.AppendLine($script:cssStyle)
$htmlText = $perObject.ToString() + $script:htmlStrings.ToString() + "</HTML>"
$fileName = Join-Path $script:documentPath $script:currentItemFileName
Save-DocumentationFile $htmlText $fileName
$script:htmlStrings = $null
}
}
function Get-HTMLObjectFileName {
param($PolicyObject)
$objName = if ($PolicyObject.Name) { [string]$PolicyObject.Name } else { 'Unnamed policy' }
$id = if ($PolicyObject.Id) { [string]$PolicyObject.Id } else { $null }
$typeId = if ($PolicyObject.PolicyType -and $PolicyObject.PolicyType.Id) { [string]$PolicyObject.PolicyType.Id } else { $null }
$suffix = if ($typeId -and $id) { " [$typeId-$id]" }
elseif ($id) { " [$id]" }
else { '' }
return Remove-InvalidFileNameChars "$objName$suffix.html"
}
function Add-HTMLHeader {
param(
[string]$HeaderText,
[int]$Level = $script:objectHeaderLevel,
[switch]$ToT,
[switch]$SkipTOC
)
if ($script:htmlStrings) {
$prefix = ""
if ($ToT) {
$prefix = "Table $($script:totAnchors.Count + 1). "
$sectionAnchor = "table-$($script:totAnchors.Count + 1)"
}
else {
$sectionAnchor = "section-$($script:sectionAnchors.Count + 1)"
}
[void]$script:htmlStrings.AppendLine("<H$Level id=`"$prefix$sectionAnchor`" class='header-level$Level'>$HeaderText</H$Level>")
$fileName = $script:currentItemFileName
}
else {
$sectionAnchor = $null
$fileName = $null
}
if ($ToT) {
$script:totAnchors += [PSCustomObject]@{
Name = $HeaderText; Anchor = $sectionAnchor; Level = $Level; FileName = $fileName
}
}
elseif (-not $SkipTOC) {
$script:sectionAnchors += [PSCustomObject]@{
Name = $HeaderText; Anchor = $sectionAnchor; Level = $Level; FileName = $fileName
}
}
}
function Add-HTMLTableItems {
param(
$PolicyObject,
[string]$TypeTitle,
$Items,
[string[]]$Properties,
[string]$LngId,
[switch]$AddCategories,
[switch]$AddSubcategories,
$CaptionOverride
)
if ($CaptionOverride) {
$caption = $CaptionOverride
}
elseif ($LngId -and $PolicyObject) {
$caption = "$((Get-LanguageString $LngId)) - $(Get-DocCaptionName $PolicyObject)"
}
elseif ($PolicyObject) {
$caption = "$(Get-DocCaptionName $PolicyObject) ($TypeTitle)"
}
else {
$caption = $TypeTitle
}
$tableText = [System.Text.StringBuilder]::new()
[void]$tableText.AppendLine("<table class='table-settings'>")
[void]$tableText.AppendLine("<tr>")
$columnCount = 0
foreach ($prop in $Properties) {
[void]$tableText.AppendLine("<th>$((Invoke-DocTranslateColumnHeader $prop.Split('.')[-1]))</th>")
$columnCount++
}
[void]$tableText.AppendLine("</tr>")
$curCategory = ""
$curSubCategory = ""
$row = 1
foreach ($itemObj in $Items) {
$additionalRowClass = ""
if ($itemObj.Category -and $curCategory -ne $itemObj.Category -and $AddCategories) {
[void]$tableText.AppendLine("<tr><td colspan=`"$columnCount`" class='category-level1'>$($itemObj.Category)</td></tr>")
$curCategory = $itemObj.Category
$curSubCategory = ""
$row = 1
}
if ($itemObj.SubCategory -and $curSubCategory -ne $itemObj.SubCategory -and $AddSubcategories) {
[void]$tableText.AppendLine("<tr><td colspan=`"$columnCount`" class='category-level2'>$($itemObj.SubCategory)</td></tr>")
$curSubCategory = $itemObj.SubCategory
$row = 1
}
if ($itemObj.PropertyIndex -is [int] -and $itemObj.PropertyIndex -eq 1) {
$additionalRowClass = "row-new-property"
}
try {
$rowClass = if (($row % 2) -eq 1) { "row-odd" } else { "row-even" }
$row++
[void]$tableText.AppendLine("<tr class='$rowClass $additionalRowClass'>")
$curCol = 1
foreach ($prop in $Properties) {
try {
$propArr = $prop.Split('.')
$tmpObj = $itemObj
$propName = $propArr[-1]
for ($x = 0; $x -lt ($propArr.Count - 1); $x++) {
$tmpObj = $tmpObj."$($propArr[$x])"
}
if ($propName -eq "Value" -and ($itemObj.FullValueTable | Measure-Object).Count -gt 0) {
[void]$tableText.AppendLine("<td><table class='table-value'><tr>")
foreach ($colProp in $itemObj.FullValueTable[0].PSObject.Properties) {
[void]$tableText.AppendLine("<th>$($colProp.Name)</th>")
}
[void]$tableText.AppendLine("</tr>")
foreach ($rowVal in $itemObj.FullValueTable) {
[void]$tableText.AppendLine("<tr>")
foreach ($colProp in $itemObj.FullValueTable[0].PSObject.Properties) {
[void]$tableText.AppendLine("<td>$($rowVal."$($colProp.Name)")</td>")
}
[void]$tableText.AppendLine("</tr>")
}
[void]$tableText.AppendLine("</table></td>")
}
else {
$style = ""
if ($curCol -eq 1 -and $itemObj.Level) {
try {
$level = [int]$itemObj.Level
$style = " style='padding-left:$((5 + ($level * 5)))px;'"
} catch {}
}
[void]$tableText.AppendLine("<td class='property-column$curCol'$style>$((Set-HTMLText $tmpObj.$propName))</td>")
}
}
catch {
Write-LogError "Failed to add property value for $prop" $_.Exception
}
$curCol++
}
}
catch {
Write-Log "Failed to process property" 2
}
finally {
[void]$tableText.AppendLine("</tr>")
}
}
[void]$tableText.AppendLine("</table>")
[void]$script:htmlStrings.Append($tableText.ToString())
Add-HTMLHeader $caption -Level 6
}
function Set-HTMLText {
param([string]$Text, [switch]$NoCodeBlock)
if (-not $Text) { return }
$txtSummary = ""
if ($Text.Length -gt 250) {
$summaryMax = 40
$idx = $Text.IndexOfAny(@("`r","`n"))
if ($idx -gt 10 -and $idx -lt 50) { $summaryMax = $idx }
$txtSummary = $Text.Substring(0, $summaryMax)
}
$code = $false
if (-not $NoCodeBlock) {
$trim = $Text.Trim()
if ($trim.StartsWith("<") -and $trim.EndsWith(">")) {
$code = $true
$Text = "<pre class='code'>$($Text.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;'))</pre>"
if ($txtSummary) {
$txtSummary = $txtSummary.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
}
}
if (-not $code) {
$Text = $Text.Replace("`r`n", "<br />").Replace("`n", "<br />").Replace('&', '&amp;')
}
if ($txtSummary) {
"<details class='description'><summary data-open='Minimize' data-close='$txtSummary...expand'></summary>$Text</details>"
}
else {
$Text
}
}
function Add-HTMLObjectScripts {
param($documentedObj)
foreach ($scriptItem in $documentedObj.Scripts) {
if (-not $scriptItem.ScriptContent -or -not $scriptItem.Caption) { continue }
[void]$script:htmlStrings.AppendLine("<pre class='code'>")
[void]$script:htmlStrings.AppendLine($scriptItem.ScriptContent)
[void]$script:htmlStrings.AppendLine("</pre>")
Add-HTMLHeader $scriptItem.Caption -Level 6 -SkipTOC
}
}
Invoke-InitializeHTMLOutput