Strip legacy UI and keep headless runtime
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,167 +0,0 @@
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.0.2'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
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 = "Cop_y"
|
||||
$subItem.Add_Click({Show-CopyBulkForm})
|
||||
$tmp.AddChild($subItem)
|
||||
}
|
||||
}
|
||||
|
||||
function Show-CopyBulkForm
|
||||
{
|
||||
$script:form = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkCopy.xaml") -AddVariables
|
||||
if(-not $script:form) { return }
|
||||
|
||||
$global:txtCopyFromPattern.Text = Get-Setting "Copy" "CopyFromPattern"
|
||||
$global:txtCopyToPattern.Text = Get-Setting "Copy" "CopyToPattern"
|
||||
|
||||
$script:copyObjects = @()
|
||||
foreach($objType in $global:lstMenuItems.ItemsSource)
|
||||
{
|
||||
if(-not $objType.Title) { continue }
|
||||
|
||||
$script:copyObjects += New-Object PSObject -Property @{
|
||||
Title = $objType.Title
|
||||
Selected = $true
|
||||
ObjectType = $objType
|
||||
}
|
||||
}
|
||||
|
||||
$column = Get-GridCheckboxColumn "Selected"
|
||||
$global:dgObjectsToCopy.Columns.Add($column)
|
||||
|
||||
$column.Header.IsChecked = $true # All items are checked by default
|
||||
$column.Header.add_Click({
|
||||
foreach($item in $global:dgObjectsToCopy.ItemsSource)
|
||||
{
|
||||
$item.Selected = $this.IsChecked
|
||||
}
|
||||
$global:dgObjectsToCopy.Items.Refresh()
|
||||
}
|
||||
)
|
||||
|
||||
# Add Object type column
|
||||
$binding = [System.Windows.Data.Binding]::new("Title")
|
||||
$column = [System.Windows.Controls.DataGridTextColumn]::new()
|
||||
$column.Header = "Object type"
|
||||
$column.IsReadOnly = $true
|
||||
$column.Binding = $binding
|
||||
$global:dgObjectsToCopy.Columns.Add($column)
|
||||
|
||||
$global:dgObjectsToCopy.ItemsSource = $script:copyObjects
|
||||
|
||||
Add-XamlEvent $script:form "btnClose" "add_click" {
|
||||
$script:form = $null
|
||||
Show-ModalObject
|
||||
}
|
||||
|
||||
Add-XamlEvent $script:form "btnStartCopy" "add_click" {
|
||||
Write-Status "Copy objects"
|
||||
Start-BulkCopyObjects
|
||||
Write-Status ""
|
||||
}
|
||||
|
||||
Show-ModalForm "Bulk Copy Objects" $script:form -HideButtons
|
||||
}
|
||||
|
||||
function Start-BulkCopyObjects
|
||||
{
|
||||
Write-Log "****************************************************************"
|
||||
Write-Log "Start bulk copy"
|
||||
Write-Log "****************************************************************"
|
||||
|
||||
$copyFrom = $global:txtCopyFromPattern.Text
|
||||
$copyTo = $global:txtCopyToPattern.Text
|
||||
|
||||
if(-not $copyFrom -or -not $copyTo)
|
||||
{
|
||||
[System.Windows.MessageBox]::Show("Both name patterns must be specified", "Error", "OK", "Error")
|
||||
return
|
||||
}
|
||||
|
||||
Save-Setting "Copy" "CopyFromPattern" $global:txtCopyFromPattern.Text
|
||||
Save-Setting "Copy" "CopyToPattern" $global:txtCopyToPattern.Text
|
||||
|
||||
foreach($item in ($global:dgObjectsToCopy.ItemsSource | where Selected -eq $true))
|
||||
{
|
||||
Write-Status "Copy $($item.ObjectType.Title) objects" -Force -SkipLog
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
Write-Log "Copy $($item.ObjectType.Title) objects"
|
||||
Write-Log "----------------------------------------------------------------"
|
||||
|
||||
[array]$graphObjects = Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType
|
||||
|
||||
$nameProp = ?? $item.ObjectType.NameProperty "displayName"
|
||||
|
||||
foreach($graphObj in ($graphObjects | Where { $_.Object."$($nameProp)" -imatch [regex]::Escape($copyFrom) }))
|
||||
{
|
||||
$sourceName = $graphObj.Object."$($nameProp)"
|
||||
$copyName = $sourceName -ireplace [regex]::Escape($copyFrom),$copyTo
|
||||
|
||||
$copyObj = $graphObjects | Where { $_.Object."$($nameProp)" -eq $copyName -and $_.Object.'@OData.Type' -eq $graphObj.Object.'@OData.Type' }
|
||||
|
||||
if(($copyObj | measure).Count -gt 0)
|
||||
{
|
||||
Write-Log "Object with name $copyName already exists. $sourceName will not be copied" 2
|
||||
continue
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Status "Create $copyName from $sourceName" -Force
|
||||
|
||||
if($graphObj.ObjectType.PreCopyCommand)
|
||||
{
|
||||
if((& $graphObj.ObjectType.PreCopyCommand $graphObj.Object $graphObj.ObjectType $copyName))
|
||||
{
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$copyFromObj = (Get-GraphObject $graphObj.Object $graphObj.ObjectType -SkipAssignments).Object
|
||||
|
||||
# Convert to Json and back to clone the object
|
||||
$obj = ConvertTo-Json $copyFromObj -Depth 10 | ConvertFrom-Json
|
||||
if($obj)
|
||||
{
|
||||
# Import new profile
|
||||
Set-GraphObjectName $obj $graphObj.ObjectType $copyName
|
||||
|
||||
$newObj = Import-GraphObject $obj $graphObj.ObjectType
|
||||
if($newObj)
|
||||
{
|
||||
if($graphObj.ObjectType.PostCopyCommand)
|
||||
{
|
||||
& $graphObj.ObjectType.PostCopyCommand $copyFromObj $newObj $graphObj.ObjectType
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-log "Failed to copy $sourceName" 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "****************************************************************"
|
||||
Write-Log "Bulk copy finished"
|
||||
Write-Log "****************************************************************"
|
||||
Write-Status ""
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,664 +0,0 @@
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.1.0'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
Add-OutputType ([PSCustomObject]@{
|
||||
Name="HTML"
|
||||
Value="html"
|
||||
OutputOptions = (Add-HTMLOptionsControl)
|
||||
#Activate = { Invoke-HTMLActivate @args }
|
||||
PreProcess = { Invoke-HTMLPreProcessItems @args }
|
||||
NewObjectGroup = { Invoke-HTMLNewObjectGroup2 @args }
|
||||
NewObjectType = { Invoke-HTMLNewObjectType2 @args }
|
||||
Process = { Invoke-HTMLProcessItem @args }
|
||||
PostProcess = { Invoke-HTMLPostProcessItems @args }
|
||||
ProcessAllObjects = { Invoke-HTMLProcessAllObjects @args }
|
||||
})
|
||||
}
|
||||
|
||||
function Add-HTMLOptionsControl
|
||||
{
|
||||
$script:htmlForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\DocumentationHTMLOptions.xaml") -AddVariables
|
||||
|
||||
Set-XamlProperty $script:htmlForm "cbHTMLDocumentOutputFile" "ItemsSource" ("[ { Name: `"Single file`",Value: `"Full`" }, { Name: `"One file per object`",Value: `"Object`" }]" | ConvertFrom-Json)
|
||||
Set-XamlProperty $script:htmlForm "cbHTMLDocumentOutputFile" "SelectedValue" (Get-Setting "Documentation" "HTMLDocumentFileType" "Full")
|
||||
Set-XamlProperty $script:htmlForm "chkHTMLOpenDocument" "IsChecked" (Get-Setting "Documentation" "HTMLOpenFile" $true)
|
||||
|
||||
Set-XamlProperty $script:htmlForm "txtHTMLDocumentName" "Text" (Get-Setting "Documentation" "HTMLDocumentName" "")
|
||||
Set-XamlProperty $script:htmlForm "txtHTMLCSSFile" "Text" (Get-Setting "Documentation" "HTMLCSSFile" "")
|
||||
|
||||
Add-XamlEvent $script:htmlForm "browseHTMLDocumentName" "add_click" {
|
||||
$sf = [System.Windows.Forms.SaveFileDialog]::new()
|
||||
$sf.DefaultExt = "*.html"
|
||||
$sf.Filter = "HTML (*.html)|*.html|All files (*.*)|*.*"
|
||||
if($sf.ShowDialog() -eq "OK")
|
||||
{
|
||||
Set-XamlProperty $script:htmlForm "txtHTMLDocumentName" "Text" $sf.FileName
|
||||
Save-Setting "Documentation" "HTMLDocumentName" $sf.FileName
|
||||
}
|
||||
}
|
||||
|
||||
Add-XamlEvent $script:htmlForm "browseHTMLCSSFile" "add_click" {
|
||||
$of = [System.Windows.Forms.OpenFileDialog]::new()
|
||||
$of.Multiselect = $false
|
||||
$of.Filter = "CSS Files (*.css)|*.css|All files (*.*)|*.*"
|
||||
if($of.ShowDialog())
|
||||
{
|
||||
Set-XamlProperty $script:htmlForm "txtHTMLCSSFile" "Text" $of.FileName
|
||||
Save-Setting "Documentation" "HTMLCSSFile" $of.FileName
|
||||
}
|
||||
}
|
||||
|
||||
$script:htmlForm
|
||||
}
|
||||
function Invoke-HTMLPreProcessItems
|
||||
{
|
||||
$script:sectionAnchors = @()
|
||||
$script:totAnchors = @()
|
||||
$script:htmlStrings = $null
|
||||
$script:currentItemFileName = $null
|
||||
|
||||
Save-Setting "Documentation" "HTMLDocumentName" (Get-XamlProperty $script:htmlForm "txtHTMLDocumentName" "Text" "")
|
||||
Save-Setting "Documentation" "HTMLCSSFile" (Get-XamlProperty $script:htmlForm "txtHTMLCSSFile" "Text" "")
|
||||
Save-Setting "Documentation" "HTMLOpenFile" (Get-XamlProperty $script:htmlForm "chkHTMLOpenDocument" "IsChecked")
|
||||
Save-Setting "Documentation" "HTMLDocumentFileType" (Get-XamlProperty $script:htmlForm "cbHTMLDocumentOutputFile" "SelectedValue" '')
|
||||
|
||||
$defaultCSSFile = $global:AppRootFolder + "\Documentation\DefaultHTMLStyle.css"
|
||||
$HTMLCssFile = Get-XamlProperty $script:htmlForm "txtHTMLCSSFile" "Text" $defaultCSSFile
|
||||
|
||||
if(-not $HTMLCssFile)
|
||||
{
|
||||
Write-Log "CSS file not specified. Using default" 2
|
||||
$HTMLCssFile = $defaultCSSFile
|
||||
}
|
||||
elseif([IO.File]::Exists($HTMLCssFile) -eq -$false)
|
||||
{
|
||||
Write-Log "CSS file $($HTMLCssFile) not found. Using default" 2
|
||||
$HTMLCssFile = $defaultCSSFile
|
||||
}
|
||||
|
||||
$cssStyle = ""
|
||||
if([IO.File]::Exists($HTMLCssFile))
|
||||
{
|
||||
Write-Log "Using CSS file $($HTMLCssFile)"
|
||||
$cssStyle = ((Get-Content -Raw -Path $HTMLCssFile) + [System.Environment]::NewLine)
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "CSS file $($HTMLCssFile) not found. No styles applied" 2
|
||||
}
|
||||
|
||||
$script:cssStyle = $cssStyle
|
||||
|
||||
$fileName = Expand-FileName (Get-XamlProperty $script:htmlForm "txtHTMLDocumentName" "Text" "%MyDocuments%\%Organization%-%Date%.html")
|
||||
|
||||
$script:outFile = $fileName
|
||||
$script:documentPath = [io.path]::GetDirectoryName($fileName)
|
||||
|
||||
$script:outputType = (Get-XamlProperty $script:htmlForm "cbHTMLDocumentOutputFile" "SelectedValue" "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 = $global:me.displayName
|
||||
if($global:me.givenName -and $global:me.surname)
|
||||
{
|
||||
$userName = ($global:me.givenName + " " + $global:me.surname)
|
||||
}
|
||||
|
||||
$script:htmlContent = [System.Text.StringBuilder]::new()
|
||||
$script:htmlContent.AppendLine("<HTML>")
|
||||
|
||||
$script:htmlContent.AppendLine($script:cssStyle)
|
||||
|
||||
$script:htmlContent.AppendLine("<H1 class='header-level1'>$((?? $script:htmlDocTitle "Intune documentation"))</H1>")
|
||||
|
||||
$mail = ""
|
||||
if($global:me.mail)
|
||||
{
|
||||
$mail = " ($($global:me.mail))"
|
||||
}
|
||||
|
||||
$script:htmlContent.AppendLine("Organization: $($global:Organization.displayName)<br />")
|
||||
$script:htmlContent.AppendLine("Generated by: $userName$mail<br />")
|
||||
$script:htmlContent.AppendLine("Generated: $((Get-Date).ToShortDateString()) $((Get-Date).ToLongTimeString())<br />")
|
||||
|
||||
if($script:sectionAnchors.Count -gt 0)
|
||||
{
|
||||
$script:htmlContent.AppendLine("<br />")
|
||||
$script: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
|
||||
}
|
||||
|
||||
$script: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)
|
||||
{
|
||||
$script:htmlContent.AppendLine("<br />")
|
||||
}
|
||||
|
||||
$htmlText = $script:htmlContent.ToString()
|
||||
if($script:outputType -eq "Full")
|
||||
{
|
||||
$htmlText += $script:htmlStrings.ToString()
|
||||
}
|
||||
$htmlText += "</HTML>"
|
||||
|
||||
Save-DocumentationFile $htmlText $script:outFile -OpenFile:((Get-Setting "Documentation" "HTMLOpenFile" $true) -eq $true)
|
||||
}
|
||||
|
||||
function Invoke-HTMLNewObjectGroup
|
||||
{
|
||||
param($obj, $documentedObj)
|
||||
|
||||
$script:objectHeaderLevel = 2
|
||||
|
||||
$objectTypeString = Get-ObjectTypeString $obj.Object $obj.ObjectType
|
||||
|
||||
Add-HTMLHeader (?? $objectTypeString $obj.ObjectType.Title)
|
||||
}
|
||||
|
||||
|
||||
function Invoke-HTMLNewObjectType
|
||||
{
|
||||
param($obj, $documentedObj, [int]$groupCategoryCount = 0)
|
||||
|
||||
$script:objectHeaderLevel = 3
|
||||
|
||||
if($obj.ObjectType.GroupId -eq "EndpointSecurity")
|
||||
{
|
||||
$objectTypeString = $obj.CategoryName
|
||||
}
|
||||
else
|
||||
{
|
||||
$objectTypeString = $obj.ObjectType.Title
|
||||
}
|
||||
|
||||
Add-HTMLHeader (?? $objectTypeString $obj.ObjectType.Title)
|
||||
|
||||
$script:objectHeaderLevel = 4
|
||||
}
|
||||
|
||||
function Invoke-HTMLNewObjectGroup2
|
||||
{
|
||||
param($groupId)
|
||||
|
||||
$script:objectHeaderLevel = 2
|
||||
|
||||
$objectTypeString = Get-ObjectTypeString -ObjectType $groupId
|
||||
|
||||
Add-HTMLHeader (?? $objectTypeString $obj.ObjectType.Title)
|
||||
}
|
||||
|
||||
function Invoke-HTMLNewObjectType2
|
||||
{
|
||||
param($objectTypeName)
|
||||
|
||||
$script:objectHeaderLevel = 3
|
||||
|
||||
Add-HTMLHeader $objectTypeName
|
||||
|
||||
$script:objectHeaderLevel = 4
|
||||
}
|
||||
|
||||
function Add-HTMLHeader
|
||||
{
|
||||
param ($headerText, [int]$level = $script:objectHeaderLevel, [switch]$ToT, [switch]$SkipTOC)
|
||||
|
||||
if($script:htmlStrings)
|
||||
{
|
||||
$prefix = ""
|
||||
if($ToT -eq $true)
|
||||
{
|
||||
$prefix = "Table $(($script:totAnchors.Count + 1)). "
|
||||
}
|
||||
|
||||
if($ToT -eq $true)
|
||||
{
|
||||
$sectionAnchor = "table-$(($script:totAnchors.Count + 1))"
|
||||
}
|
||||
else
|
||||
{
|
||||
$sectionAnchor = "section-$(($script:sectionAnchors.Count + 1))"
|
||||
}
|
||||
|
||||
$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 -eq $true)
|
||||
{
|
||||
$script:totAnchors += [PSCustomObject]@{
|
||||
Name = $headerText
|
||||
Anchor = $sectionAnchor
|
||||
Level = $level
|
||||
FileName = $FileName
|
||||
}
|
||||
}
|
||||
elseif($SkipTOC -ne $true)
|
||||
{
|
||||
$script:sectionAnchors += [PSCustomObject]@{
|
||||
Name = $headerText
|
||||
Anchor = $sectionAnchor
|
||||
Level = $level
|
||||
FileName = $FileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Invoke-HTMLProcessItem
|
||||
{
|
||||
param($obj, $objectType, $documentedObj)
|
||||
|
||||
if(!$documentedObj -or !$obj -or !$objectType) { return }
|
||||
|
||||
$objName = Get-GraphObjectName $obj $objectType
|
||||
|
||||
if($script:outputType -eq "Object")
|
||||
{
|
||||
$script:totAnchors = @()
|
||||
$script:htmlStrings = [System.Text.StringBuilder]::new()
|
||||
$script:currentItemFileName = (Remove-InvalidFileNameChars "$($objName).html")
|
||||
}
|
||||
|
||||
Add-HTMLHeader $objName
|
||||
|
||||
$script:htmlStrings.AppendLine("<br />")
|
||||
|
||||
try
|
||||
{
|
||||
foreach($tableType in @("BasicInfo","FilteredSettings"))
|
||||
{
|
||||
if($tableType -eq "BasicInfo")
|
||||
{
|
||||
$properties = @("Name","Value")
|
||||
}
|
||||
elseif($global:txtHTMLDocumentationProperties.SelectedValue -eq 'extended' -and $documentedObj.DisplayProperties)
|
||||
{
|
||||
$properties = @("Name","Value","Description")
|
||||
}
|
||||
elseif($global:txtHTMLDocumentationProperties.SelectedValue -eq 'custom' -and $global:txtHTMLCustomProperties.Text)
|
||||
{
|
||||
$properties = @()
|
||||
|
||||
foreach($prop in $global:txtHTMLCustomProperties.Text.Split(","))
|
||||
{
|
||||
# This will add language support for custom columens (or replacing existing header)
|
||||
$propInfo = $prop.Split('=')
|
||||
if(($propInfo | measure).Count -gt 1)
|
||||
{
|
||||
$properties += $propInfo[0]
|
||||
Set-DocColumnHeaderLanguageId $propInfo[0] $propInfo[1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$properties += $prop
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$properties = (?? $documentedObj.DefaultDocumentationProperties (@("Name","Value")))
|
||||
}
|
||||
|
||||
$lngId = ?: ($tableType -eq "BasicInfo") "SettingDetails.basics" "TableHeaders.settings" -AddCategories
|
||||
|
||||
if(($documentedObj.$tableType).Count -gt 0) {
|
||||
Add-HTMLTableItems $obj $objectType ($documentedObj.$tableType) $properties $lngId -AddCategories -AddSubcategories
|
||||
}
|
||||
}
|
||||
|
||||
if(($documentedObj.ComplianceActions | measure).Count -gt 0)
|
||||
{
|
||||
$properties = @("Action","Schedule","MessageTemplate","EmailCC")
|
||||
|
||||
Add-HTMLTableItems $obj $objectType $documentedObj.ComplianceActions $properties "Category.complianceActionsLabel"
|
||||
}
|
||||
|
||||
if(($documentedObj.ApplicabilityRules | measure).Count -gt 0)
|
||||
{
|
||||
$properties = @("Rule","Property","Value")
|
||||
|
||||
Add-HTMLTableItems $obj $objectType $documentedObj.ApplicabilityRules $properties "SettingDetails.applicabilityRules"
|
||||
}
|
||||
|
||||
Add-HTMLObjectSettings $obj $objectType $documentedObj
|
||||
|
||||
foreach($customTable in ($documentedObj.CustomTables | Sort-Object -Property Order))
|
||||
{
|
||||
Add-HTMLTableItems $obj $objectType $customTable.Values $customTable.Columns $customTable.LanguageId -AddCategories -AddSubcategories
|
||||
}
|
||||
|
||||
if(($documentedObj.Assignments | measure).Count -gt 0)
|
||||
{
|
||||
if($documentedObj.Assignments[0].RawIntent)
|
||||
{
|
||||
$properties = @("GroupMode","Group","Filter","FilterMode")
|
||||
|
||||
$settingsObj = $documentedObj.Assignments | Where { $_.Settings -ne $null } | Select -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
|
||||
{
|
||||
$isFilterAssignment = $false
|
||||
foreach($assignment in $documentedObj.Assignments)
|
||||
{
|
||||
if(($assignment.PSObject.Properties | Where Name -eq "FilterMode"))
|
||||
{
|
||||
$isFilterAssignment = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
$properties = @("Group")
|
||||
if($isFilterAssignment)
|
||||
{
|
||||
$properties += @("Filter","FilterMode")
|
||||
}
|
||||
}
|
||||
|
||||
Add-HTMLTableItems $obj $objectType $documentedObj.Assignments $properties "TableHeaders.assignments" -AddCategories
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to process object $objName" $_.Exception
|
||||
}
|
||||
|
||||
if($script:outputType -eq "Object")
|
||||
{
|
||||
$script:htmlContent = [System.Text.StringBuilder]::new()
|
||||
$script:htmlContent.AppendLine("<HTML>")
|
||||
$script:htmlContent.AppendLine($script:cssStyle)
|
||||
$htmlText = $script:htmlContent.ToString()
|
||||
$htmlText += $script:htmlStrings.ToString()
|
||||
$htmlText += "</HTML>"
|
||||
|
||||
$fileName = "$($script:documentPath)\$($script:currentItemFileName)"
|
||||
Save-DocumentationFile $htmlText $fileName
|
||||
$script:htmlStrings = $null
|
||||
}
|
||||
}
|
||||
|
||||
function Add-HTMLTableItems
|
||||
{
|
||||
param($obj, $objectType, $items, $properties, $lngId, [switch]$AddCategories, [switch]$AddSubcategories, $captionOverride)
|
||||
|
||||
if($captionOverride)
|
||||
{
|
||||
$caption = $captionOverride
|
||||
}
|
||||
elseif($lngId)
|
||||
{
|
||||
$caption = "$((Get-LanguageString $lngId)) - $((Get-GraphObjectName $obj $objectType))"
|
||||
}
|
||||
else
|
||||
{
|
||||
$caption = "$((Get-GraphObjectName $obj $objectType)) ($($objectType.Title))"
|
||||
}
|
||||
|
||||
$tableText = [System.Text.StringBuilder]::new()
|
||||
$tableText.AppendLine("<table class='table-settings'>")
|
||||
# Add Header row
|
||||
$tableText.AppendLine("<tr>")
|
||||
$columnCount = 0
|
||||
foreach($prop in $properties)
|
||||
{
|
||||
$tableText.AppendLine("<th>$((Invoke-DocTranslateColumnHeader $prop.Split(".")[-1]))</th>")
|
||||
$columnCount++
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curCategory = ""
|
||||
$curSubCategory = ""
|
||||
|
||||
$columnCategory = $null
|
||||
$columnSubCategory = $null
|
||||
|
||||
$row = 1
|
||||
|
||||
foreach($itemObj in $items)
|
||||
{
|
||||
$newCategory = $false
|
||||
$newSubCategory = $false
|
||||
$additionalRowClass = ""
|
||||
if($itemObj.Category -and $curCategory -ne $itemObj.Category -and $AddCategories -eq $true)
|
||||
{
|
||||
# Add Category row
|
||||
$tableText.AppendLine("<tr>")
|
||||
$tableText.AppendLine("<td colspan=`"$($columnCount)`" class='category-level1'>$($itemObj.Category)</td>")
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curCategory = $itemObj.Category
|
||||
$curSubCategory = ""
|
||||
$row = 1
|
||||
$newCategory = $true
|
||||
$curentPropertyIndex = 0
|
||||
}
|
||||
|
||||
if($itemObj.SubCategory -and $curSubCategory -ne $itemObj.SubCategory -and $AddSubcategories -eq $true)
|
||||
{
|
||||
# Add Sub-category row
|
||||
$tableText.AppendLine("<tr>")
|
||||
$tableText.AppendLine("<td colspan=`"$($columnCount)`" class='category-level2'>$($itemObj.SubCategory)</td>")
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curSubCategory = $itemObj.SubCategory
|
||||
$row = 1
|
||||
$newSubCategory = $true
|
||||
$curentPropertyIndex = 0
|
||||
}
|
||||
|
||||
if($itemObj.PropertyIndex -is [int] -and $itemObj.PropertyIndex -gt 0 -and $itemObj.PropertyIndex -eq 1)
|
||||
{
|
||||
$curentPropertyIndex = $itemObj.PropertyIndex
|
||||
$additionalRowClass = "row-new-property"
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(($row % 2) -eq 1)
|
||||
{
|
||||
$rowClass = "row-odd"
|
||||
}
|
||||
else
|
||||
{
|
||||
$rowClass = "row-even"
|
||||
}
|
||||
|
||||
$row++
|
||||
|
||||
$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).Count -gt 0)
|
||||
{
|
||||
$tableText.AppendLine("<td><table class='table-value'>")
|
||||
$tableText.AppendLine("<tr>")
|
||||
foreach($tableObjectProp in $itemObj.FullValueTable[0].PSObject.Properties)
|
||||
{
|
||||
$tableText.AppendLine("<th>$($tableObjectProp.Name)</th>")
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
foreach($tableValue in $itemObj.FullValueTable)
|
||||
{
|
||||
$tableText.AppendLine("<tr>")
|
||||
foreach($tableObjectProp in $itemObj.FullValueTable[0].PSObject.Properties)
|
||||
{
|
||||
$tableText.AppendLine("<td>$($tableValue."$($tableObjectProp.Name)")</td>")
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
}
|
||||
$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{}
|
||||
}
|
||||
$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
|
||||
{
|
||||
$tableText.AppendLine("</tr>")
|
||||
}
|
||||
}
|
||||
|
||||
$tableText.AppendLine("</table>")
|
||||
$script:htmlStrings.Append($tableText.ToString())
|
||||
Add-HTMLHeader $caption -level 6
|
||||
}
|
||||
|
||||
function Invoke-HTMLProcessAllObjects
|
||||
{
|
||||
param($documentationInfo)
|
||||
|
||||
$scopeTagInfo = Get-TableObjects "ScopeTags"
|
||||
|
||||
if($allObjectTypeObjdocumentationInfoects.ObjectType.Id -eq "ScopeTags")
|
||||
{
|
||||
if(($documentationInfo.Items | measure).Count -gt 0)
|
||||
{
|
||||
Add-HTMLHeader $documentationInfo.TypeName
|
||||
|
||||
Add-HTMLTableItems $null $documentationInfo.ObjectType $documentationInfo.Items -captionOverride (Get-LanguageString "SettingDetails.scopeTags")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Set-HTMLText
|
||||
{
|
||||
param([string]$text, [switch]$NoCodeBlock)
|
||||
|
||||
if(-not $text)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$txtSummary = ""
|
||||
if($text -and $text.Length -gt 250)
|
||||
{
|
||||
$summaryMax = 40
|
||||
# Show the first row or the first $max characters if first row is too short or too long
|
||||
$idx = $text.IndexOfAny(@("`r","`n"))
|
||||
if($idx -gt 10 -and $idx -lt 50)
|
||||
{
|
||||
$summaryMax = $idx
|
||||
}
|
||||
$txtSummary = $text.SubString(0,$summaryMax)
|
||||
}
|
||||
|
||||
$code = $false
|
||||
if($NoCodeBlock -ne $true)
|
||||
{
|
||||
$trimText = $text.Trim()
|
||||
if($trimText.StartsWith("<") -and $trimText.EndsWith(">"))
|
||||
{
|
||||
$code = $true
|
||||
$text = "<pre class='code'>$($text.Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace('"', '"'))</pre>"
|
||||
if($txtSummary)
|
||||
{
|
||||
$txtSummary = $txtSummary.Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace('"', '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($code -eq $false)
|
||||
{
|
||||
$text = $text.Replace("`r`n", "<br />")
|
||||
$text = $text.Replace("`n", "<br />")
|
||||
$text = $text.Replace('&', '&')
|
||||
}
|
||||
|
||||
if($txtSummary)
|
||||
{
|
||||
"<details class='description'><summary data-open='Minimize' data-close='$($txtSummary)...expand'></summary>$text</details>"
|
||||
}
|
||||
else
|
||||
{
|
||||
$text
|
||||
}
|
||||
}
|
||||
|
||||
function Add-HTMLObjectSettings
|
||||
{
|
||||
param($obj, $objectType, $documentedObj)
|
||||
|
||||
foreach($objectScript in $documentedObj.Scripts)
|
||||
{
|
||||
if(-not $objectScript.ScriptContent -or -not $objectScript.Caption) { continue }
|
||||
$script:htmlStrings.AppendLine("<pre class='code'>")
|
||||
$script:htmlStrings.AppendLine($objectScript.ScriptContent)
|
||||
$script:htmlStrings.AppendLine("</pre>")
|
||||
Add-HTMLHeader $objectScript.Caption -Level 6 -SkipTOC
|
||||
}
|
||||
}
|
||||
@@ -1,676 +0,0 @@
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.2.0'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
Add-OutputType ([PSCustomObject]@{
|
||||
Name="Markdown"
|
||||
Value="md"
|
||||
OutputOptions = (Add-MDOptionsControl)
|
||||
#Activate = { Invoke-MDActivate @args }
|
||||
PreProcess = { Invoke-MDPreProcessItems @args }
|
||||
NewObjectGroup = { Invoke-MDNewObjectGroup2 @args }
|
||||
NewObjectType = { Invoke-MDNewObjectType2 @args }
|
||||
Process = { Invoke-MDProcessItem @args }
|
||||
PostProcess = { Invoke-MDPostProcessItems @args }
|
||||
ProcessAllObjects = { Invoke-MDProcessAllObjects @args }
|
||||
})
|
||||
}
|
||||
|
||||
function Add-MDOptionsControl
|
||||
{
|
||||
$script:mdForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\DocumentationMDOptions.xaml") -AddVariables
|
||||
|
||||
Set-XamlProperty $script:mdForm "txtMDDocumentName" "Text" (Get-Setting "Documentation" "MDDocumentName" "")
|
||||
Set-XamlProperty $script:mdForm "txtMDCSSFile" "Text" (Get-Setting "Documentation" "MDCSSFile" "")
|
||||
Set-XamlProperty $script:mdForm "chkMDIncludeCSS" "IsChecked" (Get-Setting "Documentation" "MDIncludeCSS" $true)
|
||||
Set-XamlProperty $script:mdForm "chkMDOpenDocument" "IsChecked" (Get-Setting "Documentation" "MDOpenFile" $true)
|
||||
Set-XamlProperty $script:mdForm "cbMDDocumentOutputFile" "ItemsSource" ("[ { Name: `"Single file`",Value: `"Full`" }, { Name: `"One file per object`",Value: `"Object`" }]" | ConvertFrom-Json)
|
||||
Set-XamlProperty $script:mdForm "cbMDDocumentOutputFile" "SelectedValue" (Get-Setting "Documentation" "MDDocumentFileType" "Full")
|
||||
Set-XamlProperty $script:mdForm "cbMDDocumentSkipDate" "IsChecked" ((Get-Setting "Documentation" "MDDocumentSkipDate" "False") -eq "True")
|
||||
|
||||
Add-XamlEvent $script:mdForm "browseMDDocumentName" "add_click" {
|
||||
$sf = [System.Windows.Forms.SaveFileDialog]::new()
|
||||
$sf.DefaultExt = "*.md"
|
||||
$sf.Filter = "MD (*.md)|*.md|All files (*.*)|*.*"
|
||||
if($sf.ShowDialog() -eq "OK")
|
||||
{
|
||||
Set-XamlProperty $script:MDForm "txtMDDocumentName" "Text" $sf.FileName
|
||||
Save-Setting "Documentation" "MDDocumentName" $sf.FileName
|
||||
}
|
||||
}
|
||||
|
||||
Add-XamlEvent $script:mdForm "browseMDCSSFile" "add_click" {
|
||||
$of = [System.Windows.Forms.OpenFileDialog]::new()
|
||||
$of.Multiselect = $false
|
||||
$of.Filter = "CSS Files (*.css)|*.css|All files (*.*)|*.*"
|
||||
if($of.ShowDialog())
|
||||
{
|
||||
Set-XamlProperty $script:mdForm "txtMDCSSFile" "Text" $of.FileName
|
||||
Save-Setting "Documentation" "txtMDCSSFile" $of.FileName
|
||||
}
|
||||
}
|
||||
|
||||
$script:mdForm
|
||||
}
|
||||
|
||||
function Invoke-MDProcessAllObjects
|
||||
{
|
||||
param($allObjectTypeObjects, $objectType)
|
||||
}
|
||||
|
||||
function Invoke-MDPreProcessItems
|
||||
{
|
||||
$script:sectionAnchors = @()
|
||||
$script:totAnchors = @()
|
||||
$script:mdStrings = $null
|
||||
$script:currentItemFileName = $null
|
||||
|
||||
Save-Setting "Documentation" "MDDocumentName" (Get-XamlProperty $script:mdForm "txtMDDocumentName" "Text" "")
|
||||
Save-Setting "Documentation" "MDIncludeCSS" (Get-XamlProperty $script:mdForm "chkMDIncludeCSS" "IsChecked")
|
||||
Save-Setting "Documentation" "MDCSSFile" (Get-XamlProperty $script:mdForm "txtMDCSSFile" "Text" "")
|
||||
Save-Setting "Documentation" "MDOpenFile" (Get-XamlProperty $script:mdForm "chkMDOpenDocument" "IsChecked")
|
||||
Save-Setting "Documentation" "MDDocumentFileType" (Get-XamlProperty $script:mdForm "cbMDDocumentOutputFile" "SelectedValue" '')
|
||||
Save-Setting "Documentation" "MDDocumentSkipDate" (Get-XamlProperty $script:mdForm "cbMDDocumentSkipDate" "IsChecked")
|
||||
|
||||
$defaultCSSFile = $global:AppRootFolder + "\Documentation\DefaultMDStyle.css"
|
||||
$MDCssFile = Get-XamlProperty $script:mdForm "txtMDCSSFile" "Text" $defaultCSSFile
|
||||
|
||||
if(-not $MDCssFile)
|
||||
{
|
||||
Write-Log "CSS file not specified. Using default" 2
|
||||
$MDCssFile = $defaultCSSFile
|
||||
}
|
||||
elseif([IO.File]::Exists($MDCssFile) -eq -$false)
|
||||
{
|
||||
Write-Log "CSS file $($MDCssFile) not found. Using default" 2
|
||||
$MDCssFile = $defaultCSSFile
|
||||
}
|
||||
|
||||
$cssStyle = ""
|
||||
if([IO.File]::Exists($MDCssFile))
|
||||
{
|
||||
Write-Log "Using CSS file $($MDCssFile)"
|
||||
$cssStyle = Get-Content -Raw -Path $MDCssFile
|
||||
$cssStyle += [System.Environment]::NewLine
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "CSS file $($MDCssFile) not found. No styles applied" 2
|
||||
}
|
||||
$script:cssStyle = $cssStyle
|
||||
|
||||
$fileName = Expand-FileName (Get-XamlProperty $script:mdForm "txtMDDocumentName" "Text" "%MyDocuments%\%Organization%-%Date%.md")
|
||||
|
||||
$script:outFile = $fileName
|
||||
$script:documentPath = [io.path]::GetDirectoryName($fileName)
|
||||
|
||||
$script:outputType = (Get-XamlProperty $script:mdForm "cbMDDocumentOutputFile" "SelectedValue" "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:mdStrings = [System.Text.StringBuilder]::new()
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-MDPostProcessItems
|
||||
{
|
||||
|
||||
$userName = $global:me.displayName
|
||||
if($global:me.givenName -and $global:me.surname)
|
||||
{
|
||||
$userName = ($global:me.givenName + " " + $global:me.surname)
|
||||
}
|
||||
|
||||
$script:mdContent = [System.Text.StringBuilder]::new()
|
||||
|
||||
$script:mdContent.AppendLine("# $((?? $global:txtMDTitleProperty.Text "Intune documentation"))")
|
||||
$script:mdContent.AppendLine("")
|
||||
$script:mdContent.AppendLine("")
|
||||
|
||||
$mail = ""
|
||||
if($global:me.mail)
|
||||
{
|
||||
$mail = " ($($global:me.mail))"
|
||||
}
|
||||
|
||||
$script:mdContent.AppendLine("*Organization:* $($global:Organization.displayName)`n")
|
||||
$script:mdContent.AppendLine("*Generated by:* $userName$mail`n")
|
||||
if((Get-XamlProperty $script:mdForm "cbMDDocumentSkipDate" "IsChecked") -ne $true) {
|
||||
$script:mdContent.AppendLine("*Generated:* $((Get-Date).ToShortDateString()) $((Get-Date).ToLongTimeString())`n")
|
||||
}
|
||||
|
||||
if($script:sectionAnchors.Count -gt 0)
|
||||
{
|
||||
$script:mdContent.AppendLine("")
|
||||
$script:mdContent.AppendLine("## Table of Contents")
|
||||
}
|
||||
|
||||
foreach($header in $script:sectionAnchors)
|
||||
{
|
||||
$indent = [String]::new(" ", (($header.Level - 1) * 2))
|
||||
$script:mdContent.AppendLine("$($indent)- [$($header.Name)]($($header.FileName)#$($header.Anchor))`n")
|
||||
}
|
||||
|
||||
$mdText = $script:cssStyle
|
||||
|
||||
$script:mdContent.AppendLine("")
|
||||
$mdText += $script:mdContent.ToString()
|
||||
if($script:outputType -eq "Full")
|
||||
{
|
||||
$mdText += $script:mdStrings.ToString()
|
||||
}
|
||||
|
||||
Save-DocumentationFile $mdText $script:outFile -OpenFile:((Get-Setting "Documentation" "MDOpenFile" $true) -eq $true)
|
||||
<#
|
||||
$fileName = Expand-FileName (Get-XamlProperty $script:mdForm "txtMDDocumentName" "Text" "%MyDocuments%\%Organization%-%Date%.md")
|
||||
|
||||
try
|
||||
{
|
||||
$mdText | Out-File -FilePath $fileName -Force -Encoding utf8 -ErrorAction Stop
|
||||
Write-Log "Markdown document $fileName saved successfully"
|
||||
|
||||
if((Get-Setting "Documentation" "MDOpenFile" $true) -eq $true)
|
||||
{
|
||||
Invoke-Item $fileName
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to save Markdown file: $fileName." $_.Exception
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
function Invoke-MDNewObjectGroup
|
||||
{
|
||||
param($obj, $documentedObj)
|
||||
|
||||
$objectTypeString = Get-ObjectTypeString $obj.Object $obj.ObjectType
|
||||
|
||||
Add-MDHeader "$((?? $objectTypeString $obj.ObjectType.Title))" -Level 1 -USEHtml
|
||||
}
|
||||
|
||||
function Invoke-MDNewObjectType
|
||||
{
|
||||
param($obj, $documentedObj, [int]$groupCategoryCount = 0)
|
||||
|
||||
if($obj.ObjectType.GroupId -eq "EndpointSecurity")
|
||||
{
|
||||
$objectTypeString = $obj.CategoryName
|
||||
}
|
||||
else
|
||||
{
|
||||
$objectTypeString = $obj.ObjectType.Title
|
||||
}
|
||||
|
||||
Add-MDHeader "$((?? $objectTypeString $obj.ObjectType.Title))" -Level 2 -USEHtml
|
||||
|
||||
}
|
||||
|
||||
function Invoke-MDNewObjectGroup2
|
||||
{
|
||||
param($groupId)
|
||||
|
||||
$objectTypeString = Get-ObjectTypeString -ObjectType $groupId
|
||||
|
||||
Add-MDHeader $objectTypeString -Level 1 -USEHtml
|
||||
}
|
||||
|
||||
function Invoke-MDNewObjectType2
|
||||
{
|
||||
param($objectTypeName)
|
||||
|
||||
Add-MDHeader $objectTypeName -Level 2 -USEHtml
|
||||
}
|
||||
|
||||
function Invoke-MDProcessItem
|
||||
{
|
||||
param($obj, $objectType, $documentedObj)
|
||||
|
||||
if(!$documentedObj -or !$obj -or !$objectType) { return }
|
||||
|
||||
$objName = Get-GraphObjectName $obj $objectType
|
||||
|
||||
if($script:outputType -eq "Object")
|
||||
{
|
||||
$script:totAnchors = @()
|
||||
$script:mdStrings = [System.Text.StringBuilder]::new()
|
||||
$script:currentItemFileName = "./$((Remove-InvalidFileNameChars "$($objName).md").Replace(" ","_"))"
|
||||
}
|
||||
|
||||
Add-MDHeader $objName -Level 3 -USEHtml
|
||||
|
||||
$script:mdStrings.AppendLine("")
|
||||
|
||||
try
|
||||
{
|
||||
foreach($tableType in @("BasicInfo","FilteredSettings"))
|
||||
{
|
||||
if($tableType -eq "BasicInfo")
|
||||
{
|
||||
$properties = @("Name","Value")
|
||||
}
|
||||
elseif($global:cbMDDocumentationProperties.SelectedValue -eq 'extended' -and $documentedObj.DisplayProperties)
|
||||
{
|
||||
$properties = @("Name","Value","Description")
|
||||
}
|
||||
elseif($global:cbMDDocumentationProperties.SelectedValue -eq 'custom' -and $global:txtMDCustomProperties.Text)
|
||||
{
|
||||
$properties = @()
|
||||
|
||||
foreach($prop in $global:txtMDCustomProperties.Text.Split(","))
|
||||
{
|
||||
# This will add language support for custom columns (or replacing existing header)
|
||||
$propInfo = $prop.Split('=')
|
||||
if(($propInfo | measure).Count -gt 1)
|
||||
{
|
||||
$properties += $propInfo[0]
|
||||
Set-DocColumnHeaderLanguageId $propInfo[0] $propInfo[1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$properties += $prop
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$properties = (?? $documentedObj.DefaultDocumentationProperties (@("Name","Value")))
|
||||
}
|
||||
|
||||
$lngId = ?: ($tableType -eq "BasicInfo") "SettingDetails.basics" "TableHeaders.settings" -AddCategories
|
||||
|
||||
if(($documentedObj.$tableType).Count -gt 0) {
|
||||
Add-MDTableItems $obj $objectType ($documentedObj.$tableType) $properties $lngId -AddCategories -AddSubcategories
|
||||
}
|
||||
|
||||
#Add-MDTableItems $obj $objectType ($documentedObj.$tableType) $properties $lngId `
|
||||
# -AddCategories:($global:chkMDAddCategories.IsChecked -eq $true) `
|
||||
# -AddSubcategories:($global:chkMDAddSubCategories.IsChecked -eq $true)
|
||||
}
|
||||
|
||||
if(($documentedObj.ComplianceActions | measure).Count -gt 0)
|
||||
{
|
||||
$properties = @("Action","Schedule","MessageTemplate","EmailCC")
|
||||
|
||||
Add-MDTableItems $obj $objectType $documentedObj.ComplianceActions $properties "Category.complianceActionsLabel"
|
||||
}
|
||||
|
||||
if(($documentedObj.ApplicabilityRules | measure).Count -gt 0)
|
||||
{
|
||||
$properties = @("Rule","Property","Value")
|
||||
|
||||
Add-MDTableItems $obj $objectType $documentedObj.ApplicabilityRules $properties "SettingDetails.applicabilityRules"
|
||||
}
|
||||
|
||||
Add-MDObjectSettings $obj $objectType $documentedObj
|
||||
|
||||
foreach($customTable in ($documentedObj.CustomTables | Sort-Object -Property Order))
|
||||
{
|
||||
Add-MDTableItems $obj $objectType $customTable.Values $customTable.Columns $customTable.LanguageId -AddCategories -AddSubcategories
|
||||
}
|
||||
|
||||
if(($documentedObj.Assignments | measure).Count -gt 0)
|
||||
{
|
||||
if($documentedObj.Assignments[0].RawIntent)
|
||||
{
|
||||
$properties = @("GroupMode","Group","Filter","FilterMode")
|
||||
|
||||
$settingsObj = $documentedObj.Assignments | Where { $_.Settings -ne $null } | Select -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
|
||||
{
|
||||
$isFilterAssignment = $false
|
||||
foreach($assignment in $documentedObj.Assignments)
|
||||
{
|
||||
if(($assignment.PSObject.Properties | Where Name -eq "FilterMode"))
|
||||
{
|
||||
$isFilterAssignment = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
$properties = @("Group")
|
||||
if($isFilterAssignment)
|
||||
{
|
||||
$properties += @("Filter","FilterMode")
|
||||
}
|
||||
}
|
||||
|
||||
Add-MDTableItems $obj $objectType $documentedObj.Assignments $properties "TableHeaders.assignments" -AddCategories
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-LogError "Failed to process object $objName" $_.Exception
|
||||
}
|
||||
|
||||
if($script:outputType -eq "Object")
|
||||
{
|
||||
$script:mdContent = [System.Text.StringBuilder]::new()
|
||||
$script:mdContent.AppendLine($script:cssStyle)
|
||||
$mdText = $script:mdContent.ToString()
|
||||
$mdText += $script:mdStrings.ToString()
|
||||
|
||||
$fileName = "$($script:documentPath)\$($script:currentItemFileName)"
|
||||
Save-DocumentationFile $mdText $fileName
|
||||
$script:mdStrings = $null
|
||||
}
|
||||
}
|
||||
|
||||
function Add-MDTableItems
|
||||
{
|
||||
param($obj, $objectType, $items, $properties, $lngId, [switch]$AddCategories, [switch]$AddSubcategories, $captionOverride)
|
||||
|
||||
if($captionOverride)
|
||||
{
|
||||
$caption = $captionOverride
|
||||
}
|
||||
elseif($lngId)
|
||||
{
|
||||
$caption = "$((Get-LanguageString $lngId)) - $((Get-GraphObjectName $obj $objectType))"
|
||||
}
|
||||
else
|
||||
{
|
||||
$caption = "$((Get-GraphObjectName $obj $objectType)) ($($objectType.Title))"
|
||||
}
|
||||
|
||||
$tableText = [System.Text.StringBuilder]::new()
|
||||
$tableText.AppendLine("<table class='table-settings'>")
|
||||
$tableText.AppendLine("<tr class='table-header1'>")
|
||||
|
||||
$columnCount = 0
|
||||
foreach($prop in $properties)
|
||||
{
|
||||
$tableText.AppendLine("<td>$((Invoke-DocTranslateColumnHeader $prop.Split(".")[-1]))</td>")
|
||||
$columnCount++
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curCategory = ""
|
||||
$curSubCategory = ""
|
||||
|
||||
$columnCategory = $null
|
||||
$columnSubCategory = $null
|
||||
|
||||
foreach($itemObj in $items)
|
||||
{
|
||||
$additionalRowClass = ""
|
||||
if($itemObj.Category -and $curCategory -ne $itemObj.Category -and $AddCategories -eq $true)
|
||||
{
|
||||
$tableText.AppendLine("<tr>")
|
||||
$tableText.AppendLine("<td colspan=`"$($columnCount)`" class='category-level1'>$((Set-MDText $itemObj.Category))</td>")
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curCategory = $itemObj.Category
|
||||
$curSubCategory = ""
|
||||
$curentPropertyIndex = 0
|
||||
}
|
||||
|
||||
if($itemObj.SubCategory -and $curSubCategory -ne $itemObj.SubCategory -and $AddSubcategories -eq $true)
|
||||
{
|
||||
$tableText.AppendLine("<tr>")
|
||||
$tableText.AppendLine("<td colspan=`"$($columnCount)`" class='category-level2'>$((Set-MDText $itemObj.SubCategory))</td>")
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
$curSubCategory = $itemObj.SubCategory
|
||||
$curentPropertyIndex = 0
|
||||
}
|
||||
|
||||
if($itemObj.PropertyIndex -is [int] -and $itemObj.PropertyIndex -gt 0 -and $itemObj.PropertyIndex -eq 1)
|
||||
{
|
||||
$curentPropertyIndex = $itemObj.PropertyIndex
|
||||
$additionalRowClass = "row-new-property"
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$tableText.AppendLine("<tr class='$($additionalRowClass)'>")
|
||||
|
||||
$curCol = 1
|
||||
foreach($prop in $properties)
|
||||
{
|
||||
try
|
||||
{
|
||||
# This adds support for properties like Settings.PropName
|
||||
$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).Count -gt 0)
|
||||
{
|
||||
$tableText.AppendLine("<td><table class='table-value'>")
|
||||
$tableText.AppendLine("<tr>")
|
||||
foreach($tableObjectProp in $itemObj.FullValueTable[0].PSObject.Properties)
|
||||
{
|
||||
$tableText.AppendLine("<td class='table-header1'>$($tableObjectProp.Name)</td>")
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
|
||||
foreach($tableValue in $itemObj.FullValueTable)
|
||||
{
|
||||
$tableText.AppendLine("<tr>")
|
||||
foreach($tableObjectProp in $itemObj.FullValueTable[0].PSObject.Properties)
|
||||
{
|
||||
$tableText.AppendLine("<td>$($tableValue."$($tableObjectProp.Name)")</td>")
|
||||
}
|
||||
$tableText.AppendLine("</tr>")
|
||||
}
|
||||
$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 !important;'"
|
||||
}
|
||||
catch{}
|
||||
}
|
||||
$params = @{}
|
||||
if($curCol -gt 0)
|
||||
{
|
||||
$params.Add("CodeBlock", $true)
|
||||
}
|
||||
|
||||
$tableText.AppendLine("<td class='property-column$($curCol)'$style>$((Set-MDText $tmpObj.$propName @params))</td>")
|
||||
}
|
||||
|
||||
#$columnData += "$((Set-MDText "$($tmpObj.$propName)"))|"
|
||||
}
|
||||
catch
|
||||
{
|
||||
#$columnData += "|"
|
||||
Write-LogError "Failed to add property value for $prop" $_.Exception
|
||||
}
|
||||
$curCol++
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Log "Failed to process property" 2
|
||||
}
|
||||
finally
|
||||
{
|
||||
$tableText.AppendLine("</tr>")
|
||||
}
|
||||
|
||||
#Add-MDText $columnData
|
||||
}
|
||||
|
||||
$tableText.AppendLine("</table>")
|
||||
Add-MDText $tableText.ToString()
|
||||
|
||||
Add-MDHeader $caption -Level 6 -TOT -AddParagraph
|
||||
}
|
||||
|
||||
function Add-MDText
|
||||
{
|
||||
param($text, [switch]$AddParagraph)
|
||||
|
||||
$script:mdStrings.AppendLine($text)
|
||||
|
||||
if($AddParagraph -eq $true)
|
||||
{
|
||||
# Add new paragraph by default
|
||||
$script:mdStrings.AppendLine("")
|
||||
}
|
||||
}
|
||||
|
||||
function Set-MDText
|
||||
{
|
||||
param([string]$text, [switch]$CodeBlock)
|
||||
|
||||
if($null -eq $text) { return }
|
||||
|
||||
$txtSummary = ""
|
||||
$textOut = ""
|
||||
|
||||
if($text -and $text.Length -gt 250)
|
||||
{
|
||||
$summaryMax = 40
|
||||
# Show the first row or the first $max characters if first row is too short or too long
|
||||
$idx = $text.IndexOfAny(@("`r","`n"))
|
||||
if($idx -gt 10 -and $idx -lt 50)
|
||||
{
|
||||
$summaryMax = $idx
|
||||
}
|
||||
$txtSummary = $text.SubString(0,$summaryMax)
|
||||
}
|
||||
|
||||
if($CodeBlock -eq $true)
|
||||
{
|
||||
$trimText = $text.Trim()
|
||||
if($trimText.StartsWith("<?xml") -or $trimText.StartsWith("<xml") -or ($trimText.StartsWith("<") -and $trimText.EndsWith(">")))
|
||||
{
|
||||
$textOut = ([Environment]::NewLine + [Environment]::NewLine + "``````xml" + [Environment]::NewLine + $text + [Environment]::NewLine + "``````" + [Environment]::NewLine + [Environment]::NewLine)
|
||||
}
|
||||
}
|
||||
|
||||
if($CodeBlock -eq $false -or -not $textOut)
|
||||
{
|
||||
$text = $text.Replace("|", '`|')
|
||||
$text = $text.Replace("*", '`*')
|
||||
$text = $text.Replace("$", '`$')
|
||||
$text = $text.Replace("`r`n", "<br />")
|
||||
$textOut = $text.Replace("`n", "<br />")
|
||||
}
|
||||
|
||||
if($txtSummary)
|
||||
{
|
||||
"<details class='description'><summary data-open='Minimize' data-close='$($txtSummary)...expand'></summary>$textOut</details>"
|
||||
}
|
||||
else
|
||||
{
|
||||
$textOut
|
||||
}
|
||||
}
|
||||
|
||||
function Add-MDHeader
|
||||
{
|
||||
param($text, [int]$level = 1, [switch]$AddParagraph, [switch]$UseHTML, [switch]$ToT, [switch]$SkipTOC)
|
||||
|
||||
if($script:mdStrings)
|
||||
{
|
||||
$prefix = ""
|
||||
if($ToT -eq $true)
|
||||
{
|
||||
$prefix = "Table $(($script:totAnchors.Count + 1)). "
|
||||
}
|
||||
|
||||
if($UseHTML -eq $true)
|
||||
{
|
||||
if($ToT -eq $true)
|
||||
{
|
||||
$sectionAnchor = "table-$(($script:totAnchors.Count + 1))"
|
||||
}
|
||||
else
|
||||
{
|
||||
$sectionAnchor = "section-$(($script:sectionAnchors.Count + 1))"
|
||||
}
|
||||
|
||||
$script:mdStrings.AppendLine("<h$level id=`"$prefix$($sectionAnchor)`">$text</h$level>")
|
||||
}
|
||||
else
|
||||
{
|
||||
# Warnig: Not complete! Use HTML if not working...
|
||||
$text = "$prefix$text"
|
||||
$sectionAnchor = $text.ToLower().Replace(" ","-").Replace("[","").Replace("]","")
|
||||
|
||||
$mdHeader = [String]::new('#',$level)
|
||||
$script:mdStrings.AppendLine("$mdHeader $text")
|
||||
}
|
||||
$FileName = $script:currentItemFileName
|
||||
}
|
||||
else
|
||||
{
|
||||
$sectionAnchor = $null
|
||||
$FileName = $null
|
||||
}
|
||||
|
||||
if($ToT -eq $true)
|
||||
{
|
||||
$script:totAnchors += [PSCustomObject]@{
|
||||
Name = $text
|
||||
Anchor = $sectionAnchor
|
||||
FileName = $FileName
|
||||
Level = $level
|
||||
}
|
||||
}
|
||||
elseif($SkipTOC -ne $true)
|
||||
{
|
||||
$script:sectionAnchors += [PSCustomObject]@{
|
||||
Name = $text
|
||||
Anchor = $sectionAnchor
|
||||
FileName = $FileName
|
||||
Level = $level
|
||||
}
|
||||
}
|
||||
|
||||
if($AddParagraph -eq $true)
|
||||
{
|
||||
# Add new paragraph by default
|
||||
$script:mdStrings.AppendLine("`n")
|
||||
}
|
||||
}
|
||||
|
||||
function Add-MDObjectSettings
|
||||
{
|
||||
param($obj, $objectType, $documentedObj)
|
||||
|
||||
foreach($objectScript in $documentedObj.Scripts)
|
||||
{
|
||||
if(-not $objectScript.ScriptContent -or -not $objectScript.Caption) { continue }
|
||||
|
||||
$script:mdStrings.AppendLine("~~~powershell")
|
||||
$script:mdStrings.AppendLine($objectScript.ScriptContent)
|
||||
$script:mdStrings.AppendLine("~~~")
|
||||
Add-MDHeader $objectScript.Caption -Level 6 -SkipTOC -AddParagraph
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,177 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Module for read-only Intune objects
|
||||
|
||||
.DESCRIPTION
|
||||
This module is for the Endpoint Info View. It shows read-only objects in Intune
|
||||
|
||||
.NOTES
|
||||
Author: Mikael Karlsson
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'3.9.6'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
#Add menu group and items
|
||||
$global:EMInfoViewObject = (New-Object PSObject -Property @{
|
||||
Title = "Intune Info"
|
||||
Description = "Displays read-only information in Intune."
|
||||
ID = "EMInfoGraphAPI"
|
||||
ViewPanel = $viewPanel
|
||||
AuthenticationID = "MSAL"
|
||||
AllowDelete = $false
|
||||
ItemChanged = { Show-GraphObjects -ObjectTypeChanged; Invoke-ModuleFunction "Invoke-GraphObjectsChanged"; Write-Status ""}
|
||||
Activating = { Invoke-EMInfoActivatingView }
|
||||
Authentication = (Get-MSALAuthenticationObject)
|
||||
Authenticate = { Invoke-EMInfoAuthenticateToMSAL }
|
||||
AppInfo = (Get-GraphAppInfo "EMAzureApp" $global:DefaultAzureApp "EM")
|
||||
SaveSettings = { Invoke-EMSaveSettings }
|
||||
Permissions = @()
|
||||
})
|
||||
|
||||
Add-ViewObject $global:EMInfoViewObject
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Baseline Templates - Intent"
|
||||
Id = "BaselineTemplates"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
API = "/deviceManagement/templates"
|
||||
ShowButtons = @("Export","View")
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
Icon="EndpointSecurity"
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Baseline Templates - Settings Catalog"
|
||||
Id = "BaselineTemplatesSettingsCatalog"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
API = "/deviceManagement/configurationPolicyTemplates"
|
||||
QUERYLIST = "`$filter=(templateFamily eq 'Baseline')"
|
||||
ShowButtons = @("Export","View")
|
||||
DefaultColumns = "0,displayName=Template Name,displayVersion=Version,lifecycleState=State,baseId=Template Id,id"
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
Icon="EndpointSecurity"
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Android Google Play"
|
||||
Id = "AndroidGooglePlay"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
ViewProperties = @("bindStatus", "lastAppSyncDateTime", "ownerUserPrincipalName")
|
||||
API = "/deviceManagement/androidManagedStoreAccountEnterpriseSettings"
|
||||
ShowButtons = @("Export","View")
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Android Enrolment Profiles"
|
||||
Id = "AndroidEnrolmentProfiles"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
API = "deviceManagement/androidDeviceOwnerEnrollmentProfiles"
|
||||
ShowButtons = @("Export","View")
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
Icon = "AndroidCOWP"
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Apple VPP Tokens"
|
||||
Id = "AppleVPPTokens"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
ViewProperties = @("appleId", "state", "appleId", "id")
|
||||
API = "/deviceAppManagement/vppTokens"
|
||||
ShowButtons = @("Export","View")
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Apple Enrollment Tokens"
|
||||
Id = "AppleEnrollmentTokens"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
ViewProperties = @("tokenName", "appleIdentifier", "tokenExpirationDateTime", "id")
|
||||
API = "/deviceManagement/depOnboardingSettings/?`$top=100"
|
||||
ShowButtons = @("Export","View")
|
||||
Permissons=@("DeviceManagementServiceConfig.ReadWrite.All")
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
|
||||
Add-ViewItem (New-Object PSObject -Property @{
|
||||
Title = "Tenant Settings"
|
||||
Id = "TenantSettings"
|
||||
ViewID = "EMInfoGraphAPI"
|
||||
API = "deviceManagement/settings"
|
||||
NameProperty = "Name"
|
||||
AlwaysImport = $true
|
||||
#ExportFullObject = $true
|
||||
ViewProperties = @("Name")
|
||||
ShowButtons = @("Import","Export","View")
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
PreImportCommand = { Start-PreImportTenantSettings @args }
|
||||
GetObjectName = { Start-GetObjectNameTenantSettings @args }
|
||||
PostListCommand = { Start-PostListTenantSettings @args }
|
||||
Icon="TenantSettings"
|
||||
ExpandAssignmentsList = $false
|
||||
})
|
||||
}
|
||||
|
||||
function Invoke-EMInfoActivatingView
|
||||
{
|
||||
if(-not $global:EMInfoViewObject.ViewPanel)
|
||||
{
|
||||
# Use the same view panel as Intune Manager
|
||||
$global:EMInfoViewObject.ViewPanel = $global:EMViewObject.ViewPanel
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-EMInfoAuthenticateToMSAL
|
||||
{
|
||||
$global:EMInfoViewObject.AppInfo = Get-GraphAppInfo "EMAzureApp" $global:DefaultAzureApp "EM"
|
||||
Set-MSALCurrentApp $global:EMInfoViewObject.AppInfo
|
||||
$usr = (?? $global:MSALToken.Account.UserName (Get-Setting "" "LastLoggedOnUser"))
|
||||
if($usr)
|
||||
{
|
||||
& $global:msalAuthenticator.Login -Account $usr
|
||||
}
|
||||
}
|
||||
|
||||
function Start-PreImportTenantSettings
|
||||
{
|
||||
param($obj, $objectType)
|
||||
|
||||
$objClone = $obj | ConvertTo-Json -Depth 50 | ConvertFrom-Json
|
||||
if($objClone.deviceComplianceCheckinThresholdDays -lt 1)
|
||||
{
|
||||
$objClone.deviceComplianceCheckinThresholdDays = 30
|
||||
}
|
||||
Remove-Property $objClone "@odata.type"
|
||||
$json = @{ "settings" = $objClone } | ConvertTo-Json -Depth 50
|
||||
(Invoke-GraphRequest -Url "deviceManagement" -Content $json -HttpMethod "PATCH") | Out-Null
|
||||
|
||||
return (@{"Import"=$false})
|
||||
}
|
||||
|
||||
function Start-GetObjectNameTenantSettings
|
||||
{
|
||||
param($objList, $objectType)
|
||||
|
||||
return "Tenant Settings"
|
||||
}
|
||||
|
||||
function Start-PostListTenantSettings
|
||||
{
|
||||
param($objList, $objectType)
|
||||
|
||||
if(($objList | measure).Count -eq 1)
|
||||
{
|
||||
$objList[0].Name = "Tenant Settings"
|
||||
#$objList[0] | Add-Member -MemberType NoteProperty -Name "SettingName" -Value "Tenant Settings"
|
||||
}
|
||||
$objList
|
||||
}
|
||||
@@ -1,816 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Module for Intune Applications
|
||||
|
||||
.DESCRIPTION
|
||||
This module manages Application objects in Intune e.g. uploading application files
|
||||
|
||||
.NOTES
|
||||
Author: Mikael Karlsson
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'3.9.6'
|
||||
}
|
||||
|
||||
#########################################################################################
|
||||
#
|
||||
# Upload file functions are based on the following scripts
|
||||
# https://github.com/microsoftgraph/powershell-intune-samples/tree/master/LOB_Application
|
||||
#
|
||||
#########################################################################################
|
||||
|
||||
function Export-IntunewinFileObject
|
||||
{
|
||||
param($intunewinFile, $objectName, $toFile)
|
||||
|
||||
Add-Type -Assembly System.IO.Compression.FileSystem
|
||||
|
||||
$zip = [IO.Compression.ZipFile]::OpenRead($intunewinFile)
|
||||
|
||||
$zip.Entries | where { $_.Name -like $objectName } | foreach {
|
||||
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $toFile, $true)
|
||||
}
|
||||
|
||||
$zip.Dispose()
|
||||
}
|
||||
|
||||
function Get-MSIFileInformation
|
||||
{
|
||||
param($MSIFile, $Properties)
|
||||
|
||||
$values = @{}
|
||||
|
||||
if(-not $MSIFile) { return }
|
||||
|
||||
$fi = [IO.FileInfo]$MSIFile
|
||||
|
||||
if($fi.Extension -ne ".msi") { return }
|
||||
|
||||
try
|
||||
{
|
||||
$wiObj = New-Object -ComObject WindowsInstaller.Installer
|
||||
$MSIDb = $wiObj.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $wiObj, @($MSIFile, 0))
|
||||
|
||||
foreach($prop in $Properties)
|
||||
{
|
||||
$Query = "SELECT Value FROM Property WHERE Property = '$($prop)'"
|
||||
$View = $MSIDb.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDb, ($Query))
|
||||
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null) | Out-Null
|
||||
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
|
||||
$values.Add($prop, $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1).ToString().Trim())
|
||||
}
|
||||
|
||||
$MSIDb.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDb, $null) | Out-Null
|
||||
$View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null) | Out-Null
|
||||
$MSIDb = $null
|
||||
$View = $null
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Log "Failed to get MSI info from $MSIFile. $($_.Exception.Message)" 3
|
||||
}
|
||||
finally
|
||||
{
|
||||
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wiObj) | Out-Null
|
||||
[System.GC]::Collect() | Out-Null
|
||||
}
|
||||
|
||||
$values
|
||||
}
|
||||
|
||||
function Copy-MSILOB
|
||||
{
|
||||
param($msiFile, $appObj)
|
||||
|
||||
if(-not $msiFile -or (Test-Path $msiFile) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$appId = $appObj.Id
|
||||
$appType = $appObj.'@odata.type'.Trim('#')
|
||||
|
||||
$tmpFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
$msiInfo = Get-MSIFileInformation $msiFile @("ProductName", "ProductCode", "ProductVersion", "ProductLanguage", "UpgradeCode", "ALLUSERS")
|
||||
|
||||
if(-not $msiInfo) { return }
|
||||
|
||||
$fileEncryptionInfo = New-IntuneEncryptedFile $msiFile $tmpFile
|
||||
|
||||
[xml]$manifestXML = '<MobileMsiData MsiExecutionContext="Any" MsiRequiresReboot="false" MsiUpgradeCode="" MsiIsMachineInstall="true" MsiIsUserInstall="false" MsiIncludesServices="false" MsiContainsSystemRegistryKeys="false" MsiContainsSystemFolders="false"></MobileMsiData>'
|
||||
$manifestXML.MobileMsiData.MsiUpgradeCode = $msiInfo["UpgradeCode"]
|
||||
if($msiInfo["ALLUSERS"] -eq 1)
|
||||
{
|
||||
$manifestXML.MobileMsiData.MsiExecutionContext = "System"
|
||||
}
|
||||
|
||||
$appFileBody = @{
|
||||
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
|
||||
name = [IO.Path]::GetFileName($msiFile)
|
||||
size = (Get-Item $msiFile).Length
|
||||
sizeEncrypted = (Get-Item $tmpFile).Length
|
||||
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
|
||||
isDependency = $false
|
||||
}
|
||||
|
||||
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
|
||||
|
||||
Remove-Item $tmpFile -Force
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Copy-MSIXLOB
|
||||
{
|
||||
param($msixFile, $appObj)
|
||||
|
||||
if(-not $msixFile -or (Test-Path $msixFile) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$fi = [IO.FileInfo]$msixFile
|
||||
|
||||
$appId = $appObj.Id
|
||||
$appType = $appObj.'@odata.type'.Trim('#')
|
||||
|
||||
$tmpFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
$fileEncryptionInfo = New-IntuneEncryptedFile $msixFile $tmpFile
|
||||
|
||||
$manifest = $fi.Name
|
||||
|
||||
$appFileBody = @{
|
||||
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
|
||||
name = [IO.Path]::GetFileName($msixFile)
|
||||
size = (Get-Item $msixFile).Length
|
||||
sizeEncrypted = (Get-Item $tmpFile).Length
|
||||
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifest))
|
||||
isDependency = $false
|
||||
}
|
||||
|
||||
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
|
||||
|
||||
Remove-Item $tmpFile -Force
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Copy-iOSLOB
|
||||
{
|
||||
param($pkgFile, $appObj)
|
||||
|
||||
if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$appId = $appObj.Id
|
||||
$appType = $appObj.'@odata.type'.Trim('#')
|
||||
|
||||
$tmpFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
$fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
|
||||
|
||||
[string]$manifestStr = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>items</key><array><dict><key>assets</key><array><dict><key>kind</key><string>software-package</string><key>url</key><string>{UrlPlaceHolder}</string></dict></array><key>metadata</key><dict><key>AppRestrictionPolicyTemplate</key> <string>http://management.microsoft.com/PolicyTemplates/AppRestrictions/iOS/v1</string><key>AppRestrictionTechnology</key><string>Windows Intune Application Restrictions Technology for iOS</string><key>IntuneMAMVersion</key><string></string><key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array><key>MinimumOSVersion</key><string>9.0</string><key>bundle-identifier</key><string>bundleid</string><key>bundle-version</key><string>bundleversion</string><key>kind</key><string>software</string><key>subtitle</key><string>LaunchMeSubtitle</string><key>title</key><string>bundletitle</string></dict></dict></array></dict></plist>'
|
||||
|
||||
$manifestStr = $manifestStr.replace("bundleid", $appObj.bundleId)
|
||||
$manifestStr = $manifestStr.replace("bundleversion",$appObj.identityVersion)
|
||||
$manifestStr = $manifestStr.replace("bundletitle",$appObj.$displayName)
|
||||
|
||||
$appFileBody = @{
|
||||
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
|
||||
name = [IO.Path]::GetFileName($pkgFile)
|
||||
size = (Get-Item $pkgFile).Length
|
||||
sizeEncrypted = (Get-Item $tmpFile).Length
|
||||
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestStr))
|
||||
}
|
||||
|
||||
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
|
||||
|
||||
Remove-Item $tmpFile -Force
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Copy-AndroidLOB
|
||||
{
|
||||
param($pkgFile, $appObj)
|
||||
|
||||
if(-not $pkgFile -or (Test-Path $pkgFile) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$appId = $appObj.Id
|
||||
$appType = $appObj.'@odata.type'.Trim('#')
|
||||
|
||||
$tmpFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
$fileEncryptionInfo = New-IntuneEncryptedFile $pkgFile $tmpFile
|
||||
|
||||
[xml]$manifestXML = '<?xml version="1.0" encoding="utf-8"?><AndroidManifestProperties xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Package>com.leadapps.android.radio.ncp</Package><PackageVersionCode>10</PackageVersionCode><PackageVersionName>1.0.5.4</PackageVersionName><ApplicationName>A_Online_Radio_1.0.5.4.apk</ApplicationName><MinSdkVersion>3</MinSdkVersion><AWTVersion></AWTVersion></AndroidManifestProperties>'
|
||||
|
||||
$manifestXML.AndroidManifestProperties.Package = $appObj.identityName
|
||||
$manifestXML.AndroidManifestProperties.PackageVersionCode = $appObj.versionCode
|
||||
$manifestXML.AndroidManifestProperties.PackageVersionName = $appObj.versionName
|
||||
$manifestXML.AndroidManifestProperties.ApplicationName = [IO.Path]::GetFileName($pkgFile)
|
||||
|
||||
$appFileBody = @{
|
||||
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
|
||||
name = [IO.Path]::GetFileName($pkgFile)
|
||||
size = (Get-Item $pkgFile).Length
|
||||
sizeEncrypted = (Get-Item $tmpFile).Length
|
||||
manifest = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($manifestXML.OuterXml))
|
||||
}
|
||||
|
||||
Add-FileToIntuneApp $appId $appType $tmpFile $appFileBody
|
||||
|
||||
Remove-Item $tmpFile -Force
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Copy-Win32LOBPackage
|
||||
{
|
||||
param($intunewinFile, $appObj)
|
||||
|
||||
if(-not $intunewinFile -or (Test-Path $intunewinFile) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$appId = $appObj.Id
|
||||
$appType = $appObj.'@odata.type'.Trim('#')
|
||||
|
||||
#Extract the detection.xml from the intunewin file
|
||||
|
||||
$tmpFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
Export-IntunewinFileObject $intunewinFile "detection.xml" $tmpFile
|
||||
|
||||
[xml]$DetectionXML = Get-Content $tmpFile
|
||||
|
||||
Remove-Item -Path $tmpFile
|
||||
|
||||
$fi = [IO.FileInfo]$intunewinFile
|
||||
|
||||
# Get encryption info from detection.xml and build encryptionInfo object
|
||||
|
||||
$encryptionInfo = @{}
|
||||
$encryptionInfo.encryptionKey = $DetectionXML.ApplicationInfo.EncryptionInfo.EncryptionKey
|
||||
$encryptionInfo.macKey = $DetectionXML.ApplicationInfo.EncryptionInfo.macKey
|
||||
$encryptionInfo.initializationVector = $DetectionXML.ApplicationInfo.EncryptionInfo.initializationVector
|
||||
$encryptionInfo.mac = $DetectionXML.ApplicationInfo.EncryptionInfo.mac
|
||||
$encryptionInfo.profileIdentifier = "ProfileVersion1"
|
||||
$encryptionInfo.fileDigest = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigest
|
||||
$encryptionInfo.fileDigestAlgorithm = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigestAlgorithm
|
||||
|
||||
$tmpIntunewinPath = ([IO.Path]::GetTempPath() + [Guid]::NewGuid().ToString("n"))
|
||||
mkdir $tmpIntunewinPath | Out-Null
|
||||
$tmpIntunewinFile = $tmpIntunewinPath + "\" + $fi.Name
|
||||
|
||||
# Extract the encrypted file from the intunewin file
|
||||
Export-IntunewinFileObject $intunewinFile $DetectionXML.ApplicationInfo.FileName $tmpIntunewinFile
|
||||
|
||||
# Create mobileAppContentFile object for the file
|
||||
$fileEncryptionInfo = @{}
|
||||
$fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
|
||||
|
||||
$fileBody = @{
|
||||
"@odata.type" = "#microsoft.graph.mobileAppContentFile"
|
||||
name = "IntunePackage.intunewin"
|
||||
size = [int64]$DetectionXML.ApplicationInfo.UnencryptedContentSize
|
||||
sizeEncrypted = (Get-Item $tmpIntunewinFile).Length
|
||||
manifest = $null
|
||||
isDependency = $false
|
||||
}
|
||||
|
||||
Add-FileToIntuneApp $appId $appType $tmpIntunewinFile $fileBody
|
||||
|
||||
# Remove extracted inintunewin file
|
||||
Remove-Item $tmpIntunewinPath -Force -Recurse
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Add-FileToIntuneApp
|
||||
{
|
||||
param($appId, $appType, $appFile, $fileBody)
|
||||
|
||||
$contentVersion = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions" -HttpMethod POST -Content "{}" -ODataMetadata "Minimal"
|
||||
$contentVersionId = $contentVersion.id
|
||||
$fileObj = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files" -HttpMethod POST -Content (ConvertTo-Json $fileBody -Depth 5) -ODataMetadata "Minimal"
|
||||
|
||||
if(-not $fileObj)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
Write-Log "File object created. ID: $($fileObj.id)"
|
||||
|
||||
# Wait for Azure storage URI
|
||||
$fileObj = Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "AzureStorageUriRequest"
|
||||
if(-not $fileObj)
|
||||
{
|
||||
Write-Log "No File Object returned from commit. Upload failed" 3
|
||||
return
|
||||
}
|
||||
|
||||
# Upload file
|
||||
Send-IntuneFileToAzureStorage $fileObj.azureStorageUri $appFile "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" | Out-Null
|
||||
|
||||
# Commit the file
|
||||
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)/commit" -HttpMethod POST -Content (ConvertTo-Json $fileEncryptionInfo -Depth 5) | Out-Null
|
||||
|
||||
Wait-IntuneFileState "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVersionId/files/$($fileObj.Id)" "CommitFile" | Out-Null
|
||||
|
||||
# Commit the content version
|
||||
$commitAppBody = @{
|
||||
"@odata.type" = "#$appType"
|
||||
committedContentVersion = $contentVersionId
|
||||
}
|
||||
|
||||
# if($fileBody.Name) {
|
||||
# $fileUploadName = $fileBody.Name
|
||||
# }
|
||||
# else {
|
||||
$fiUpload = [IO.FileInfo]$appFile
|
||||
$fileUploadName = $fiUpload.Name
|
||||
# }
|
||||
$commitAppBody.Add("fileName",$fileUploadName)
|
||||
|
||||
Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId" -HttpMethod PATCH -Content (ConvertTo-Json $commitAppBody -Depth 5) | Out-Null
|
||||
Write-Log "Upload finished for file $fileUploadName version $contentVersionId"
|
||||
}
|
||||
|
||||
function Wait-IntuneFileState
|
||||
{
|
||||
param($fileUri, $state, $maxWait = 60)
|
||||
|
||||
Write-Status "Wait for state $state"
|
||||
|
||||
$endWait = (Get-Date).AddMinutes($maxWait)
|
||||
|
||||
$successState = "$($state)Success"
|
||||
$pendingState = "$($state)Pending"
|
||||
$failedState = "$($state)Failed"
|
||||
$timedOutState = "$($state)TimedOut"
|
||||
|
||||
$file = $null
|
||||
$succes = $false
|
||||
|
||||
while ((Get-Date) -lt $endWait)
|
||||
{
|
||||
$file = Invoke-GraphRequest -Url $fileUri
|
||||
|
||||
if ($file.uploadState -eq $successState)
|
||||
{
|
||||
$succes = $true
|
||||
break
|
||||
}
|
||||
elseif ($file.uploadState -ne $pendingState)
|
||||
{
|
||||
Write-Log "Failed to upload file. State: $($file.uploadState)" 3
|
||||
return
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
if($succes -eq $false)
|
||||
{
|
||||
Write-Log "Wait for state operation timed out" 3
|
||||
return
|
||||
}
|
||||
|
||||
$file
|
||||
}
|
||||
|
||||
function Send-IntuneFileToAzureStorage
|
||||
{
|
||||
param($sasUri, $filepath, $fileUri)
|
||||
|
||||
try
|
||||
{
|
||||
$chunkSizeInBytes = 5MB
|
||||
|
||||
# Start the timer for SAS URI renewal.
|
||||
$sasRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
|
||||
# Find the file size and open the file.
|
||||
$fileSize = (Get-Item $filepath).length
|
||||
$chunks = [Math]::Ceiling($fileSize / $chunkSizeInBytes)
|
||||
$reader = New-Object System.IO.BinaryReader([System.IO.File]::Open($filepath, [System.IO.FileMode]::Open))
|
||||
$position = $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin)
|
||||
|
||||
# Upload each chunk. Check whether a SAS URI renewal is required after each chunk is uploaded and renew if needed.
|
||||
$ids = @()
|
||||
|
||||
for ($chunk = 0; $chunk -lt $chunks; $chunk++)
|
||||
{
|
||||
|
||||
$id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000")))
|
||||
$ids += $id
|
||||
|
||||
$start = $chunk * $chunkSizeInBytes
|
||||
$length = [Math]::Min([uint64]($chunkSizeInBytes), [uint64]($fileSize - $start))
|
||||
$bytes = $reader.ReadBytes($length)
|
||||
|
||||
$currentChunk = $chunk + 1
|
||||
|
||||
Write-Status "Uploading file to Azure Storage`n`nUploading chunk $currentChunk of $chunks ($(("{0:N2}" -f ($currentChunk / $chunks*100)))%)"
|
||||
|
||||
if((Write-AzureStorageChunk $sasUri $id $bytes) -eq $false)
|
||||
{
|
||||
Write-Log "Upload failed. Abourting..." 3
|
||||
break
|
||||
}
|
||||
|
||||
if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000)
|
||||
{
|
||||
Request-RenewAzureStorageUpload $fileUri
|
||||
$sasRenewalTimer.Restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Log "Failed to send file to Intune. $($_.Exception.Message)" 3
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($reader -ne $null)
|
||||
{
|
||||
$reader.Close()
|
||||
$reader.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
# Finalize the upload.
|
||||
$uploadResponse = Set-FinalizeAzureStorageUpload $sasUri $ids
|
||||
}
|
||||
|
||||
function Request-RenewAzureStorageUpload
|
||||
{
|
||||
param($fileUri)
|
||||
|
||||
$fileObj = Invoke-GraphRequest -Url "$fileUri/renewUpload" -HttpMethod POST
|
||||
|
||||
$file = Wait-IntuneFileState $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds
|
||||
}
|
||||
|
||||
function Set-FinalizeAzureStorageUpload
|
||||
{
|
||||
param($sasUri, $ids)
|
||||
|
||||
$uri = "$sasUri&comp=blocklist"
|
||||
|
||||
if(($uri -notmatch "^http://|^https://"))
|
||||
{
|
||||
$uri = $global:graphURL + "/" + $uri.TrimStart('/')
|
||||
}
|
||||
|
||||
$request = "PUT $uri"
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><BlockList>'
|
||||
foreach ($id in $ids)
|
||||
{
|
||||
$xml += "<Latest>$id</Latest>"
|
||||
}
|
||||
$xml += '</BlockList>'
|
||||
|
||||
$params = @{}
|
||||
$proxyURI = Get-ProxyURI
|
||||
if($proxyURI)
|
||||
{
|
||||
$params.Add("proxy", $proxyURI)
|
||||
$params.Add("UseBasicParsing", $true)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Invoke-RestMethod $uri -Method Put -Body $xml @params
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Log "Failed to finilize upload. $($_.Exception.Message)" 3
|
||||
}
|
||||
}
|
||||
|
||||
function Write-AzureStorageChunk
|
||||
{
|
||||
param($sasUri, $id, $body)
|
||||
|
||||
$uri = "$sasUri&comp=block&blockid=$id"
|
||||
|
||||
if(($uri -notmatch "^http://|^https://"))
|
||||
{
|
||||
$uri = $global:graphURL + "/" + $uri.TrimStart('/')
|
||||
}
|
||||
|
||||
$request = "PUT $uri"
|
||||
|
||||
$iso = [System.Text.Encoding]::GetEncoding("iso-8859-1")
|
||||
$encodedBody = $iso.GetString($body)
|
||||
$headers = @{
|
||||
"x-ms-blob-type" = "BlockBlob"
|
||||
"Content-Type" = "application/octet-stream"
|
||||
}
|
||||
|
||||
# In PowerShell (Core) 7 v7.4+, the web cmdlets (Invoke-WebRequest, Invoke-RestMethod)
|
||||
# consistently encode text-based request bodies as UTF-8, unless explicitly specified otherwise.
|
||||
if ($PSVersionTable.PSVersion -ge [Version]"7.4")
|
||||
{
|
||||
$headers["Content-Type"] += "; charset=iso-8859-1"
|
||||
}
|
||||
|
||||
$curProgressPreference = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$success = $false
|
||||
$retryCount = 0
|
||||
$params = @{}
|
||||
$proxyURI = Get-ProxyURI
|
||||
if($proxyURI)
|
||||
{
|
||||
$params.Add("proxy", $proxyURI)
|
||||
}
|
||||
|
||||
while($true)
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody -UseBasicParsing @params
|
||||
if($retryCount -gt 0)
|
||||
{
|
||||
Write-Log "Chunk uploaded successfully"
|
||||
}
|
||||
$success = $true
|
||||
break
|
||||
}
|
||||
catch
|
||||
{
|
||||
if($_.Exception.HResult -eq -2146233079 -and $retryCount -lt 6)
|
||||
{
|
||||
Write-Log "Failed to upload file chunk. Retry in 10 s" 2
|
||||
$retryCount++
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "Failed to upload file chunk. $($_.Exception.Message)" 3
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
$ProgressPreference = $curProgressPreference
|
||||
$success
|
||||
}
|
||||
|
||||
function Get-IntuneKey
|
||||
{
|
||||
try
|
||||
{
|
||||
$aes = [System.Security.Cryptography.Aes]::Create()
|
||||
$aesProvider = New-Object System.Security.Cryptography.AesCryptoServiceProvider
|
||||
$aesProvider.GenerateKey()
|
||||
$aesProvider.Key
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($aesProvider -ne $null) { $aesProvider.Dispose() }
|
||||
if ($aes -ne $null) { $aes.Dispose() }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-IntuneKeyIV
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
$aes = [System.Security.Cryptography.Aes]::Create()
|
||||
$aes.IV
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($aes -ne $null) { $aes.Dispose() }
|
||||
}
|
||||
}
|
||||
|
||||
function Start-EncryptFileWithIV
|
||||
{
|
||||
param($sourceFile, $targetFile, $encryptionKey, $hmacKey, $initializationVector)
|
||||
|
||||
$bufferBlockSize = 1024 * 4
|
||||
$computedMac = $null
|
||||
|
||||
try
|
||||
{
|
||||
$aes = [System.Security.Cryptography.Aes]::Create()
|
||||
$hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
|
||||
$hmacSha256.Key = $hmacKey
|
||||
$hmacLength = $hmacSha256.HashSize / 8
|
||||
|
||||
$buffer = New-Object byte[] $bufferBlockSize
|
||||
$bytesRead = 0
|
||||
|
||||
$targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
|
||||
$targetStream.Write($buffer, 0, $hmacLength + $initializationVector.Length)
|
||||
|
||||
try
|
||||
{
|
||||
$encryptor = $aes.CreateEncryptor($encryptionKey, $initializationVector)
|
||||
$sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
|
||||
$cryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
|
||||
|
||||
$targetStream = $null
|
||||
while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
|
||||
{
|
||||
$cryptoStream.Write($buffer, 0, $bytesRead)
|
||||
$cryptoStream.Flush()
|
||||
}
|
||||
$cryptoStream.FlushFinalBlock()
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($cryptoStream -ne $null) { $cryptoStream.Dispose() }
|
||||
if ($sourceStream -ne $null) { $sourceStream.Dispose() }
|
||||
if ($encryptor -ne $null) { $encryptor.Dispose() }
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$finalStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::Read)
|
||||
|
||||
$finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
|
||||
$finalStream.Write($initializationVector, 0, $initializationVector.Length)
|
||||
$finalStream.Seek($hmacLength, [System.IO.SeekOrigin]::Begin) > $null
|
||||
|
||||
$hmac = $hmacSha256.ComputeHash($finalStream)
|
||||
$computedMac = $hmac
|
||||
|
||||
$finalStream.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
|
||||
$finalStream.Write($hmac, 0, $hmac.Length)
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($finalStream -ne $null) { $finalStream.Dispose() }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($targetStream -ne $null) { $targetStream.Dispose() }
|
||||
if ($aes -ne $null) { $aes.Dispose() }
|
||||
}
|
||||
|
||||
$computedMac
|
||||
}
|
||||
|
||||
function New-IntuneEncryptedFile
|
||||
{
|
||||
param($sourceFile, $targetFile)
|
||||
|
||||
$encryptionKey = Get-IntuneKey
|
||||
$hmacKey = Get-IntuneKey
|
||||
$initializationVector = Get-IntuneKeyIV
|
||||
|
||||
# Create the encrypted target file and compute the HMAC value.
|
||||
$mac = Start-EncryptFileWithIV $sourceFile $targetFile $encryptionKey $hmacKey $initializationVector
|
||||
|
||||
# Compute the SHA256 hash of the source file and convert the result to bytes.
|
||||
$fileDigest = (Get-FileHash $sourceFile -Algorithm SHA256).Hash
|
||||
$fileDigestBytes = New-Object byte[] ($fileDigest.Length / 2)
|
||||
for ($i = 0; $i -lt $fileDigest.Length; $i += 2)
|
||||
{
|
||||
$fileDigestBytes[$i / 2] = [System.Convert]::ToByte($fileDigest.Substring($i, 2), 16)
|
||||
}
|
||||
|
||||
# Return an object that will serialize correctly to the file commit Graph API.
|
||||
$encryptionInfo = @{}
|
||||
$encryptionInfo.encryptionKey = [System.Convert]::ToBase64String($encryptionKey)
|
||||
$encryptionInfo.macKey = [System.Convert]::ToBase64String($hmacKey)
|
||||
$encryptionInfo.initializationVector = [System.Convert]::ToBase64String($initializationVector)
|
||||
$encryptionInfo.mac = [System.Convert]::ToBase64String($mac)
|
||||
$encryptionInfo.profileIdentifier = "ProfileVersion1"
|
||||
$encryptionInfo.fileDigest = [System.Convert]::ToBase64String($fileDigestBytes)
|
||||
$encryptionInfo.fileDigestAlgorithm = "SHA256"
|
||||
|
||||
$fileEncryptionInfo = @{}
|
||||
$fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo
|
||||
|
||||
$fileEncryptionInfo
|
||||
}
|
||||
|
||||
function Start-DecryptFile
|
||||
{
|
||||
param($sourceFile, $targetFile, $encryptionKey, $initializationVector)
|
||||
|
||||
if([IO.File]::Exists($targetFile))
|
||||
{
|
||||
$fi = [IO.FileInfo]$targetFile
|
||||
$newName = $fi.Name + "_$((Get-Date).ToString("yyyyMMdd_HHmm"))" + $fi.Extension
|
||||
$targetFile = $fi.DirectoryName + "\$newName"
|
||||
Write-Log "Target file exists. Changing target file to $targetFile" 2
|
||||
}
|
||||
|
||||
$bufferBlockSize = 1024 * 4
|
||||
|
||||
try
|
||||
{
|
||||
$aes = [System.Security.Cryptography.Aes]::Create()
|
||||
|
||||
$buffer = New-Object byte[] $bufferBlockSize
|
||||
$bytesRead = 0
|
||||
|
||||
$targetStream = [System.IO.File]::Open($targetFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
||||
|
||||
try
|
||||
{
|
||||
$sourceStream = [System.IO.File]::Open($sourceFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None)
|
||||
|
||||
$decryptor = $aes.CreateDecryptor([Convert]::FromBase64String($encryptionKey), [Convert]::FromBase64String($initializationVector))
|
||||
|
||||
$decryptoStream = New-Object System.Security.Cryptography.CryptoStream -ArgumentList @($targetStream, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
|
||||
|
||||
$sourceStream.Seek(48L, [System.IO.SeekOrigin]::Begin)
|
||||
|
||||
while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferBlockSize)) -gt 0)
|
||||
{
|
||||
$decryptoStream.Write($buffer, 0, $bytesRead)
|
||||
$decryptoStream.Flush()
|
||||
}
|
||||
$decryptoStream.FlushFinalBlock()
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($null -ne $decryptoStream) { $decryptoStream.Dispose() }
|
||||
if ($null -ne $targetStream) { $targetStream.Dispose() }
|
||||
if ($null -ne $decryptor) { $decryptor.Dispose() }
|
||||
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
|
||||
if ($null -ne $aes) { $aes.Dispose() }
|
||||
}
|
||||
}
|
||||
|
||||
function Start-DownloadAppContent
|
||||
{
|
||||
param($obj, $destinationFile, [switch]$GetContentFileInfoOnly)
|
||||
# Not use but kept for reference. File can be download but it will be encrypted
|
||||
|
||||
if([IO.File]::Exists($destinationFile))
|
||||
{
|
||||
try { [IO.File]::Delete($encryptionFile) }
|
||||
catch {}
|
||||
}
|
||||
|
||||
$appId = $obj.Id
|
||||
|
||||
$appInfo = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId"
|
||||
|
||||
$appType = $appInfo.'@odata.type'.Trim('#')
|
||||
|
||||
#$contentVersions = Invoke-GraphRequest -Url "$($global:graphURL)/deviceAppManagement/mobileApps/$appId/$appType/contentVersions"
|
||||
#$contentVerId = $contentVersions.Value[0].id
|
||||
|
||||
$contentVerId = $appInfo.committedContentVersion
|
||||
|
||||
$contentFiles = Invoke-GraphRequest "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files"
|
||||
|
||||
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($contentFiles.value[-1].Id)" -NoError
|
||||
|
||||
if(-not $contentFile)
|
||||
{
|
||||
foreach($file in $contentFiles.value)
|
||||
{
|
||||
if($contentFiles.value[-1].Id -eq $file.id) { continune }
|
||||
|
||||
# NOT happy about this. file objects are not always returned in the order of upload.
|
||||
$contentFile = Invoke-GraphRequest -Url "/deviceAppManagement/mobileApps/$appId/$appType/contentVersions/$contentVerId/files/$($file.Id)" -NoError
|
||||
if($contentFile)
|
||||
{
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($contentFile.azureStorageUri)
|
||||
{
|
||||
if($GetContentFileInfoOnly -ne $true)
|
||||
{
|
||||
Start-DownloadFile $contentFile.azureStorageUri $destinationFile
|
||||
}
|
||||
return $contentFile
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Log "Could not find file object for app $($obj.displayName) ($($appId))" 2
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Module for listing Intune assignments
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.NOTES
|
||||
Author: Mikael Karlsson
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.0.3'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
Add-EMToolsViewItem (New-Object PSObject -Property @{
|
||||
Title = "Intune Assignments"
|
||||
Id = "IntuneAssignments"
|
||||
ViewID = "EMTools"
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
Icon="DeviceConfiguration"
|
||||
ShowViewItem = { Show-EMToolsIntuneAssignments }
|
||||
})
|
||||
}
|
||||
|
||||
function Show-EMToolsIntuneAssignments
|
||||
{
|
||||
if(-not $script:frmIntuneAssignments)
|
||||
{
|
||||
$script:frmIntuneAssignments = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsIntuneAssignments.xaml") #-AddVariables
|
||||
|
||||
if(-not $script:frmIntuneAssignments) { return }
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "btnBrowseIntuneAssignmentsExportPath" "add_click" ({
|
||||
$folder = Get-Folder (Get-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text") "Select root folder for exported files"
|
||||
if($folder)
|
||||
{
|
||||
Set-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text" $folder
|
||||
}
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "btnGetIntuneAssignments" "add_click" ({
|
||||
$folder = Get-XamlProperty $script:frmIntuneAssignments "txtIntuneAssignmentsExportPath" "Text"
|
||||
if($folder)
|
||||
{
|
||||
Write-Status "Get Intune Assignments"
|
||||
Get-EMIntuneAssignments $folder
|
||||
Write-Status ""
|
||||
}
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "btnIntuneAssignmentsCopy" "add_click" ({
|
||||
$script:objAssignments | Select Name, Type, IncludedString, ExcludedString | ConvertTo-Csv -NoTypeInformation | Set-Clipboard
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "btnIntuneAssignmentsSave" "add_click" ({
|
||||
|
||||
$dlgSave = New-Object -Typename System.Windows.Forms.SaveFileDialog
|
||||
#$dlgSave.InitialDirectory = Get-SettingValue "IntuneRootFolder" $env:Temp
|
||||
$dlgSave.FileName = $obj.FileName
|
||||
$dlgSave.DefaultExt = "*.csv"
|
||||
$dlgSave.Filter = "CSV (*.csv)|*.csv|All files (*.*)| *.*"
|
||||
if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename)
|
||||
{
|
||||
$script:objAssignments | Select Name, Type, IncludedString, ExcludedString | ConvertTo-Csv -NoTypeInformation | Out-File -LiteralPath $dlgSave.Filename -Encoding UTF8 -Force
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$global:grdToolsMain.Children.Clear()
|
||||
$global:grdToolsMain.Children.Add($frmIntuneAssignments)
|
||||
}
|
||||
|
||||
function Get-EMIntuneAssignmentInfo
|
||||
{
|
||||
param($rootDir)
|
||||
|
||||
Write-Status "Gather Export Information"
|
||||
|
||||
$path = "$rootDir\Groups"
|
||||
|
||||
$script:htGroups = @{}
|
||||
|
||||
foreach($file in (Get-Item -path "$path\*.json"))
|
||||
{
|
||||
$graphObj = (ConvertFrom-Json (Get-Content -LiteralPath $file.FullName -Raw))
|
||||
$htGroups.Add($graphObj.Id, $graphObj)
|
||||
}
|
||||
|
||||
$script:fileArr = @()
|
||||
|
||||
foreach($path in [IO.Directory]::EnumerateDirectories($rootDir))
|
||||
{
|
||||
if($path -eq "$rootDir\Groups") { continue }
|
||||
|
||||
foreach($file in (Get-Item -path "$path\*.json" -Exclude @("*_settings.json","*_assignments.json")))
|
||||
{
|
||||
$graphObj = (ConvertFrom-Json (Get-Content -LiteralPath $file.FullName -Raw))
|
||||
|
||||
$obj = New-Object PSObject -Property @{
|
||||
FileName = $file.Name
|
||||
FileInfo = $file
|
||||
Selected = $SelectedStatus
|
||||
Object = $graphObj
|
||||
}
|
||||
|
||||
$script:fileArr += $obj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-EMIntuneAssignments
|
||||
{
|
||||
param($folder)
|
||||
|
||||
Set-XamlProperty $script:frmIntuneAssignments "dgIntuneAssignments" "ItemsSource" $null
|
||||
|
||||
$folderDI = [IO.DirectoryInfo]$folder
|
||||
if(-not $folderDI.Exists) { return }
|
||||
|
||||
Get-EMIntuneAssignmentInfo $folder
|
||||
|
||||
Write-Status "Collect exported assignments"
|
||||
|
||||
$intuneViewObj = $global:viewObjects | Where { $_.ViewInfo.ID -eq "IntuneGraphAPI" }
|
||||
|
||||
$script:objAssignments = @()
|
||||
|
||||
foreach($fileObj in $script:fileArr)
|
||||
{
|
||||
$objectType = $null
|
||||
$folderName = $fileObj.FileInfo.Directory.Name
|
||||
if($folderName)
|
||||
{
|
||||
$objectType = $intuneViewObj.ViewItems | Where Id -eq $folderName
|
||||
}
|
||||
|
||||
$obj = New-Object PSObject -Property @{
|
||||
Object = $fileObj.Object
|
||||
Name = $fileObj.Object."$((?? $objectType.NameProperty "displayName"))"
|
||||
Type = $null
|
||||
Included = $null
|
||||
Excluded = $null
|
||||
IncludedString = ""
|
||||
ExcludedString = ""
|
||||
}
|
||||
$obj.Included = @()
|
||||
$obj.Excluded = @()
|
||||
if($fileObj.Object.'@OData.Type')
|
||||
{
|
||||
$obj.Type = $fileObj.Object.'@OData.Type'.Split('.')[-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj.Type = $file.Directory.Parent.Name
|
||||
}
|
||||
|
||||
foreach($assignment in $fileObj.Object.assignments)
|
||||
{
|
||||
$assignmentObj = $null
|
||||
$included = $true
|
||||
|
||||
if($assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget" -or
|
||||
$assignment.target.'@odata.type' -eq "#microsoft.graph.exclusionGroupAssignmentTarget")
|
||||
{
|
||||
if($script:htGroups.ContainsKey($assignment.target.groupId))
|
||||
{
|
||||
$assignmentObj = $script:htGroups[$assignment.target.groupId].displayName
|
||||
}
|
||||
else
|
||||
{
|
||||
$assignmentObj = $assignment.target.groupId
|
||||
Write-Warning "Could not find a group with ID $($assignment.target.groupId)"
|
||||
}
|
||||
$included = $assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget"
|
||||
}
|
||||
elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allDevicesAssignmentTarget")
|
||||
{
|
||||
$assignmentObj = "All Devices"
|
||||
}
|
||||
elseif($assignment.target.'@odata.type' -eq "#microsoft.graph.allLicensedUsersAssignmentTarget")
|
||||
{
|
||||
$assignmentObj = "All Users"
|
||||
}
|
||||
|
||||
if($included)
|
||||
{
|
||||
$obj.Included += $assignmentObj
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj.Excluded += $assignmentObj
|
||||
}
|
||||
}
|
||||
$obj.IncludedString = $obj.Included -join ";"
|
||||
$obj.ExcludedString = $obj.Excluded -join ";"
|
||||
|
||||
$script:objAssignments += $obj
|
||||
}
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_LostFocus" ({
|
||||
Invoke-IntueAssignmentFilterBoxChanged $this
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_GotFocus" ({
|
||||
if($this.Tag -eq "1" -and $this.Text -eq "Filter") { $this.Text = "" }
|
||||
Invoke-IntueAssignmentFilterBoxChanged $this ($script:frmIntuneAssignments.FindName("dgIntuneAssignments"))
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneAssignments "txtIntuneAssignmentsFilter" "Add_TextChanged" ({
|
||||
Invoke-IntueAssignmentFilterBoxChanged $this ($script:frmIntuneAssignments.FindName("dgIntuneAssignments"))
|
||||
})
|
||||
|
||||
Invoke-IntueAssignmentFilterBoxChanged ($script:frmIntuneAssignments.FindName("txtIntuneAssignmentsFilter")) ($script:frmIntuneAssignments.FindName("dgIntuneAssignments"))
|
||||
|
||||
$ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:objAssignments))
|
||||
|
||||
Set-XamlProperty $script:frmIntuneAssignments "dgIntuneAssignments" "ItemsSource" ([System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList))
|
||||
}
|
||||
|
||||
function Invoke-IntueAssignmentFilterBoxChanged
|
||||
{
|
||||
param($txtBox, $dgObject)
|
||||
|
||||
$filter = $null
|
||||
|
||||
if($txtBox.Text.Trim() -eq "" -and $txtBox.IsFocused -eq $false)
|
||||
{
|
||||
$txtBox.FontStyle = "Italic"
|
||||
$txtBox.Tag = 1
|
||||
$txtBox.Text = "Filter"
|
||||
$txtBox.Foreground="Lightgray"
|
||||
}
|
||||
elseif($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$txtBox.FontStyle = "Normal"
|
||||
$txtBox.Tag = $null
|
||||
$txtBox.Foreground="Black"
|
||||
$txtBox.Background="White"
|
||||
|
||||
if($txtBox.Text)
|
||||
{
|
||||
$filter = {
|
||||
param ($item)
|
||||
|
||||
return ($item.Name -match [regex]::Escape($txtBox.Text) -or $item.IncludedString -match [regex]::Escape($txtBox.Text) -or $item.ExcludedString -match [regex]::Escape($txtBox.Text) )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($dgObject.ItemsSource -is [System.Windows.Data.ListCollectionView] -and $txtBox.IsFocused -eq $true)
|
||||
{
|
||||
# This causes odd behaviour with focus e.g. and item has to be clicked twice to be selected
|
||||
$dgObject.ItemsSource.Filter = $filter
|
||||
#$dgObject.ItemsSource.Refresh()
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Module for listing Intune assignment filter usage
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
.NOTES
|
||||
Author: Mikael Karlsson
|
||||
#>
|
||||
function Get-ModuleVersion
|
||||
{
|
||||
'1.1.1'
|
||||
}
|
||||
|
||||
function Invoke-InitializeModule
|
||||
{
|
||||
Add-EMToolsViewItem (New-Object PSObject -Property @{
|
||||
Title = "Intune Filter Usage"
|
||||
Id = "IntuneFilterUsage"
|
||||
ViewID = "EMTools"
|
||||
Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
|
||||
Icon="DeviceConfiguration"
|
||||
ShowViewItem = { Show-IntuneToolsFilterUsage }
|
||||
})
|
||||
}
|
||||
|
||||
function Show-IntuneToolsFilterUsage
|
||||
{
|
||||
if(-not $script:frmIntuneFilterUsage)
|
||||
{
|
||||
$script:frmIntuneFilterUsage = Get-XamlObject ($global:AppRootFolder + "\Xaml\IntuneToolsFiterUsage.xaml") #-AddVariables
|
||||
|
||||
if(-not $script:frmIntuneFilterUsage) { return }
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "btnGetIntuneFilterUsage" "add_click" ({
|
||||
Write-Status "Get Intune Filter Usage"
|
||||
Get-EMIntuneFilterUsage
|
||||
Write-Status ""
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "btnIntuneFilterUsageCopy" "add_click" ({
|
||||
$dgValues = Get-DataGridValues ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage"))
|
||||
$dgValues | ConvertTo-Csv -NoTypeInformation | Set-Clipboard
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "btnIntuneFilterUsagesSave" "add_click" ({
|
||||
|
||||
$dlgSave = New-Object -Typename System.Windows.Forms.SaveFileDialog
|
||||
$dlgSave.FileName = $obj.FileName
|
||||
$dlgSave.DefaultExt = "*.csv"
|
||||
$dlgSave.Filter = "CSV (*.csv)|*.csv|All files (*.*)| *.*"
|
||||
if($dlgSave.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dlgSave.Filename)
|
||||
{
|
||||
$dgValues = Get-DataGridValues ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage"))
|
||||
$dgValues | ConvertTo-Csv -NoTypeInformation | Out-File -LiteralPath $dlgSave.Filename -Encoding UTF8 -Force
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$global:grdToolsMain.Children.Clear()
|
||||
$global:grdToolsMain.Children.Add($frmIntuneFilterUsage)
|
||||
}
|
||||
|
||||
function Get-DataGridValues_old
|
||||
{
|
||||
param($dataGrid)
|
||||
|
||||
$dgColumns = $dataGrid.Columns
|
||||
#$dgColumns = Get-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "Columns"
|
||||
|
||||
$properties = @()
|
||||
|
||||
foreach($tmpCol in $dgColumns)
|
||||
{
|
||||
$propName = $tmpCol.Binding.Path.Path
|
||||
$properties += @{n=$tmpCol.Header;e=([Scriptblock]::Create("`$_.$propName"))}
|
||||
}
|
||||
|
||||
($script:objFilterUsage | Select -Property $properties)
|
||||
}
|
||||
|
||||
function Get-EMIntuneFilterUsage
|
||||
{
|
||||
param($rootDir)
|
||||
|
||||
Write-Status "Gather Intune Filter Information"
|
||||
|
||||
Set-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "ItemsSource" $null
|
||||
|
||||
$objectType = Get-GraphObjectType "AssignmentFilters"
|
||||
|
||||
$loadedGroups = @{}
|
||||
$loadedGroups.Add("adadadad-808e-44e2-905a-0b7873a8a531","All Devices")
|
||||
$loadedGroups.Add("acacacac-9df4-4c7d-9d50-4ef0226f57a9","All Users")
|
||||
|
||||
$script:objFilters = (Invoke-GraphRequest -Url $objectType.API).Value
|
||||
|
||||
$script:objFilterUsage = @()
|
||||
$groupIDs = @()
|
||||
|
||||
foreach($filter in $script:objFilters)
|
||||
{
|
||||
Write-Status "Get payloads for filter $($filter.displayName)"
|
||||
|
||||
$payloadsManual = @()
|
||||
|
||||
$payloads = (Invoke-GraphRequest -Url "$($objectType.API)/$($filter.ID)/payloads").value
|
||||
|
||||
$batchObjs = @()
|
||||
foreach($payload in $payloads)
|
||||
{
|
||||
$guid = [Guid]::NewGuid().Guid
|
||||
|
||||
$payloadsObj = @{
|
||||
Payload = $payload
|
||||
ID = $guid
|
||||
Requests = @()
|
||||
}
|
||||
|
||||
if($groupIDs -notcontains $payload.groupId)
|
||||
{
|
||||
$groupIDs += $payload.groupId
|
||||
}
|
||||
|
||||
$batchObjs += $payloadsObj
|
||||
|
||||
if($payload.payloadType -eq "win32app")
|
||||
{
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_deviceHealthScripts"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/deviceHealthScripts/$($payload.payloadId)/?`$select=displayName,isGlobalScript"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
}
|
||||
elseif($payload.payloadType -eq "application")
|
||||
{
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_mobileApps"
|
||||
method = "GET"
|
||||
url = "/deviceAppManagement/mobileApps/$($payload.payloadId)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
}
|
||||
elseif($payload.payloadType -eq "deviceManagmentConfigurationAndCompliancePolicy")
|
||||
{
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_configurationPolicies"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/configurationPolicies/$($payload.payloadId)/?`$select=name,platforms,technologies,templateReference"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
}
|
||||
elseif($payload.payloadType -eq "groupPolicyConfiguration")
|
||||
{
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_groupPolicyConfigurations"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/groupPolicyConfigurations/$($payload.payloadId)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
}
|
||||
elseif($payload.payloadType -eq "enrollmentConfiguration")
|
||||
{
|
||||
if(-not $script:enrolmentConfigurations)
|
||||
{
|
||||
$script:enrolmentConfigurations = @()
|
||||
$script:enrolmentConfigurations += (Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations?`$select=displayName,id,deviceEnrollmentConfigurationType").value
|
||||
$script:enrolmentConfigurations += (Invoke-GraphRequest -Url "/deviceManagement/deviceEnrollmentConfigurations?`$select=displayName,id,deviceEnrollmentConfigurationType&`$filter=deviceEnrollmentConfigurationType eq 'EnrollmentNotificationsConfiguration'").value
|
||||
}
|
||||
|
||||
$payloadsManual += $payload
|
||||
|
||||
<#
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_enrollmentConfiguration"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/deviceEnrollmentConfigurations/$($enrolmentConfig.Id)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
#>
|
||||
}
|
||||
else
|
||||
{
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_deviceCompliancePolicies"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/deviceCompliancePolicies/$($payload.payloadId)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_deviceConfigurations"
|
||||
method = "GET"
|
||||
url = "/deviceManagement/deviceConfigurations/$($payload.payloadId)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
|
||||
$payloadsObj.Requests += [ordered]@{
|
||||
id = "$($guid)_mobileAppConfigurations"
|
||||
method = "GET"
|
||||
url = "/deviceAppManagement/mobileAppConfigurations/$($payload.payloadId)/?`$select=displayName"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadNames_BatchItem"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($batchObjs.Count -gt 0)
|
||||
{
|
||||
$objName = Get-GraphObjectName $filter $objectType
|
||||
$responses = Invoke-GraphBatchRequest @($batchObjs.Requests) $objName -SkipWarnings
|
||||
|
||||
foreach($response in ($responses | Where Status -lt 300))
|
||||
{
|
||||
$payload = ($batchObjs | Where { $response.id -like "$($_.ID)*"}).Payload
|
||||
|
||||
if($payload.assignmentFilterType -eq "Include")
|
||||
{
|
||||
$filterType = "Include"
|
||||
}
|
||||
else
|
||||
{
|
||||
$filterType = "Exclude"
|
||||
}
|
||||
|
||||
$typeStr = $null
|
||||
if($payload.payloadType -eq "application")
|
||||
{
|
||||
$typeStr = Get-LanguageString "AppType.windowsClassicApp"
|
||||
}
|
||||
elseif($payload.payloadType -eq "win32app")
|
||||
{
|
||||
$typeStr = "Proactive Remediations"
|
||||
}
|
||||
elseif($payload.payloadType -eq "groupPolicyConfiguration")
|
||||
{
|
||||
$typeStr = "Settings Catalog"
|
||||
}
|
||||
elseif($payload.payloadType -eq "deviceManagmentConfigurationAndCompliancePolicy")
|
||||
{
|
||||
$typeStr = "Administrative Templates"
|
||||
}
|
||||
else
|
||||
{
|
||||
$typeStr = (Get-PolicyTypeName $response.body.'@odata.type' $payload.payloadType)
|
||||
}
|
||||
|
||||
if(-not $typeStr) { $typeStr = $payload.payloadType}
|
||||
|
||||
$script:objFilterUsage += [PSCustomObject]@{
|
||||
FiterObject = $filter
|
||||
PayloadObject = $payload
|
||||
FilterName = $filter.displayName
|
||||
PolicyName = ?? $response.body.Name $response.body.displayName
|
||||
Type = $response.body.'@odata.type'
|
||||
PayloadType = $typeStr
|
||||
Mode = $filterType
|
||||
GroupID = $payload.groupId
|
||||
GroupName = $payload.groupId
|
||||
}
|
||||
}
|
||||
|
||||
foreach($response in ($responses | Where Status -ge 300))
|
||||
{
|
||||
$payload = ($batchObjs | Where { $response.id -like "$($_.ID)*"}).Payload
|
||||
Write-Log "Failed to get info for payload with id $($payload.payloadId) of type $($payload.payloadType). Might be deleted or not supported." 2
|
||||
}
|
||||
}
|
||||
|
||||
foreach($payload in $payloadsManual)
|
||||
{
|
||||
$payloadPolicy = $script:enrolmentConfigurations | Where Id -like "$($payload.payloadId)*" | Select -First 1
|
||||
|
||||
if($payloadPolicy)
|
||||
{
|
||||
if($payloadPolicy.deviceEnrollmentConfigurationType -eq "enrollmentNotificationsConfiguration")
|
||||
{
|
||||
$typeStr = "Enrollment notifications"
|
||||
}
|
||||
elseif($payloadPolicy.deviceEnrollmentConfigurationType -eq "windows10EnrollmentCompletionPageConfiguration")
|
||||
{
|
||||
$typeStr = "Enrollment Status Page"
|
||||
}
|
||||
else
|
||||
{
|
||||
$typeStr = (Get-PolicyTypeName $payloadPolicy.body.'@odata.type' $payload.payloadType)
|
||||
}
|
||||
|
||||
if($payload.assignmentFilterType -eq "Include")
|
||||
{
|
||||
$filterType = "Include"
|
||||
}
|
||||
else
|
||||
{
|
||||
$filterType = "Exclude"
|
||||
}
|
||||
|
||||
$script:objFilterUsage += [PSCustomObject]@{
|
||||
FiterObject = $filter
|
||||
PayloadObject = $payload
|
||||
FilterName = $filter.displayName
|
||||
PolicyName = ?? $payloadPolicy.Name $payloadPolicy.displayName
|
||||
Type = $payloadPolicy.'@odata.type'
|
||||
PayloadType = $typeStr
|
||||
Mode = $filterType
|
||||
GroupID = $payload.groupId
|
||||
GroupName = $payload.groupId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($groupIDs.Count -gt 0)
|
||||
{
|
||||
$guid = [Guid]::NewGuid().Guid
|
||||
$groupObjs = @()
|
||||
$x = 1
|
||||
foreach($groupID in $groupIDs)
|
||||
{
|
||||
if($loadedGroups.ContainsKey($groupID)) { continue }
|
||||
$groupObjs += [ordered]@{
|
||||
id= "$($guid)_$x"
|
||||
method="GET"
|
||||
url="/groups/$($groupID)/?`$select=displayName,id"
|
||||
headers = @{"x-ms-command-name"="AssignmentFilterPayloadProxy_resolvePayloadGroupAssignments_BatchItem"}
|
||||
}
|
||||
$x++
|
||||
}
|
||||
|
||||
if($groupObjs.Count -gt 0)
|
||||
{
|
||||
$responses = Invoke-GraphBatchRequest $groupObjs "Groups"
|
||||
|
||||
$batchObj = [ordered]@{
|
||||
requests = @($groupObjs)
|
||||
}
|
||||
|
||||
$responses = (Invoke-GraphRequest -Url "`$batch" -Body ($batchObj | ConvertTo-Json -Depth 50 -Compress) -Method "POST").responses
|
||||
|
||||
foreach($response in ($responses | Where Status -eq 200))
|
||||
{
|
||||
if($response.body.displayName -and $response.body.id -and $loadedGroups.ContainsKey($response.body.id) -eq $false)
|
||||
{
|
||||
$loadedGroups.Add($response.body.id, $response.body.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($groupID in $loadedGroups.Keys)
|
||||
{
|
||||
$filterObjs = $script:objFilterUsage | WHere GroupID -eq $groupID
|
||||
if($filterObjs -and $loadedGroups[$groupID])
|
||||
{
|
||||
foreach($filterObj in $filterObjs) {
|
||||
$filterObj.GroupName = $loadedGroups[$groupID]
|
||||
}
|
||||
}
|
||||
}
|
||||
$script:enrolmentConfigurations = $null
|
||||
}
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_LostFocus" ({
|
||||
Invoke-IntueFilterUsageBoxChanged $this
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_GotFocus" ({
|
||||
if($this.Tag -eq "1" -and $this.Text -eq "Filter") { $this.Text = "" }
|
||||
Invoke-IntueFilterUsageBoxChanged $this ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage"))
|
||||
})
|
||||
|
||||
Add-XamlEvent $script:frmIntuneFilterUsage "txtIntuneFilterUsageFilter" "Add_TextChanged" ({
|
||||
Invoke-IntueFilterUsageBoxChanged $this ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage"))
|
||||
})
|
||||
|
||||
Invoke-IntueFilterUsageBoxChanged ($script:frmIntuneFilterUsage.FindName("txtIntuneFilterUsageFilter")) ($script:frmIntuneFilterUsage.FindName("dgIntuneFilterUsage"))
|
||||
|
||||
$ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:objFilterUsage))
|
||||
|
||||
Set-XamlProperty $script:frmIntuneFilterUsage "dgIntuneFilterUsage" "ItemsSource" ([System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList))
|
||||
}
|
||||
|
||||
function Invoke-IntueFilterUsageBoxChanged
|
||||
{
|
||||
param($txtBox, $dgObject)
|
||||
|
||||
$filter = $null
|
||||
|
||||
if($txtBox.Text.Trim() -eq "" -and $txtBox.IsFocused -eq $false)
|
||||
{
|
||||
$txtBox.FontStyle = "Italic"
|
||||
$txtBox.Tag = 1
|
||||
$txtBox.Text = "Filter"
|
||||
$txtBox.Foreground="Lightgray"
|
||||
}
|
||||
elseif($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$txtBox.FontStyle = "Normal"
|
||||
$txtBox.Tag = $null
|
||||
$txtBox.Foreground="Black"
|
||||
$txtBox.Background="White"
|
||||
|
||||
if($txtBox.Text)
|
||||
{
|
||||
$filter = {
|
||||
param ($item)
|
||||
|
||||
return ($item.FilterName -match [regex]::Escape($txtBox.Text) -or $item.PolicyName -match [regex]::Escape($txtBox.Text) -or $item.GroupName -match [regex]::Escape($txtBox.Text) )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($dgObject.ItemsSource -is [System.Windows.Data.ListCollectionView] -and $txtBox.IsFocused -eq $true)
|
||||
{
|
||||
# This causes odd behaviour with focus e.g. and item has to be clicked twice to be selected
|
||||
$dgObject.ItemsSource.Filter = $filter
|
||||
#$dgObject.ItemsSource.Refresh()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user