This commit is contained in:
Mikael Karlsson
2021-12-14 02:04:15 +11:00
parent 2b9efd5c85
commit bc48d14176
16 changed files with 1112 additions and 115 deletions

View File

@@ -11,7 +11,7 @@ This module handles the WPF UI
function Get-ModuleVersion
{
'3.3.1'
'3.3.2'
}
function Start-CoreApp
@@ -784,7 +784,7 @@ function Remove-Property
function Get-GridCheckboxColumn
{
param($bindingProperty = "IsSelected")
param($bindingProperty = "IsSelected", [scriptblock]$scriptBlock)
$binding = [System.Windows.Data.Binding]::new($bindingProperty)
$binding.UpdateSourceTrigger = [System.Windows.Data.UpdateSourceTrigger]::PropertyChanged
@@ -792,6 +792,11 @@ function Get-GridCheckboxColumn
$fef = [System.Windows.FrameworkElementFactory]::new([System.Windows.Controls.CheckBox])
$binding.Mode = [System.Windows.Data.BindingMode]::TwoWay
$fef.SetValue([System.Windows.Controls.CheckBox]::IsCheckedProperty,$binding)
if($null -ne $scriptBlock)
{
[System.Windows.RoutedEventHandler]$checkedEventHandler = $scriptBlock
$fef.AddHandler([System.Windows.Controls.CheckBox]::CheckedEvent, $checkedEventHandler)
}
$dt = [System.Windows.DataTemplate]::new()
$dt.VisualTree = $fef
$column.CellTemplate = $dt
@@ -799,6 +804,10 @@ function Get-GridCheckboxColumn
$header.Margin = [System.Windows.Thickness]::new(-4,0,0,0) # Align header checkbox with the row checkboxes
$header.ToolTip = "Select/deselect all items"
$column.Header = $header
if($null -ne $scriptBlock)
{
#$header.add_click($scriptBlock)
}
$column
}

View File

@@ -54,6 +54,8 @@ This is the first version of the documentation support.
* Some Endpoint Security/Settings Catalog items is not translated based on Graph API in the portal e.g. *Antivirus - Windows 10 and Windows Server (ConfigMgr)* policies. These will be documented based on Graph API information which might be different compared to the portal
* Markdown is currently in experimental state. The script can document to an MD file created in the Documents folder but this can be to large in environments with many objects. The script will create HTML tables to support code blocks and column span. The MD View must support HTML tables to display the document. The *Markdown Viewer* extension in Chrome was used during testing.
Please create an [Issue](https://github.com/Micke-K/IntuneManagement/issues) if properties are documented incorrectly or missing.
# Deep Dive

View File

@@ -11,7 +11,7 @@ Objects can be compared based on Properties or Documentatation info.
function Get-ModuleVersion
{
'1.0.8'
'1.0.9'
}
function Invoke-InitializeModule
@@ -127,9 +127,6 @@ function Invoke-ShowMainWindow
$button.Margin = "0,0,5,0"
$button.IsEnabled = $false
$button.ToolTip = "Compare object with exported file"
$global:dgObjects.add_selectionChanged({
Set-XamlProperty $global:dgObjects.Parent "btnCompare" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null) $false $true)
})
$button.Add_Click({
Show-CompareForm $global:dgObjects.SelectedItem
@@ -140,6 +137,12 @@ function Invoke-ShowMainWindow
$global:spSubMenu.Children.Insert(0, $button)
}
function Invoke-EMSelectedItemsChanged
{
$hasSelectedItems = ($global:dgObjects.ItemsSource | Where IsSelected -eq $true) -or ($null -ne $global:dgObjects.SelectedItem)
Set-XamlProperty $global:dgObjects.Parent "btnCompare" "IsEnabled" $hasSelectedItems
}
function Invoke-ViewActivated
{
if($global:currentViewObject.ViewInfo.ID -ne "IntuneGraphAPI") { return }

View File

@@ -20,13 +20,38 @@ $global:documentationProviders = @()
function Get-ModuleVersion
{
'1.0.7'
'1.0.8'
}
function Invoke-InitializeModule
{
# Make sure we add the default Output types
Add-OutputType
$script:columnHeaders = @{
Name="Inputs.displayNameLabel"
Value="TableHeaders.value"
Description="TableHeaders.description"
GroupMode="SettingDetails.modeTableHeader" #assignmentTypeSelectionLabel?
Group="TableHeaders.assignedGroups"
Groups="TableHeaders.groups"
useDeviceContext="SettingDetails.installContextLabel"
uninstallOnDeviceRemoval="SettingDetails.UninstallOnRemoval"
isRemovable="SettingDetails.installAsRemovable"
vpnConfigurationId="PolicyType.vpn"
Action="SettingDetails.actionColumnName"
Schedule="ScheduledAction.List.schedule"
MessageTemplate="ScheduledAction.Notification.messageTemplate"
EmailCC="ScheduledAction.Notification.additionalRecipients"
Filter="AssignmentFilters.assignmentFilterColumnHeader"
Rule="ApplicabilityRules.GridLabel.Rule"
ValueWithLabel="TableHeaders.value"
Status="TableHeaders.status"
CombinedValueWithLabel="TableHeaders.value"
CombinedValue="TableHeaders.value"
useDeviceLicensing="TableHeaders.licenseType"
#filterMode="Filter mode" # Not in any string file yet
}
}
function Invoke-ShowMainWindow
@@ -38,12 +63,6 @@ function Invoke-ShowMainWindow
$button.Margin = "0,0,5,0"
$button.IsEnabled = $false
$button.ToolTip = "Document selected objects"
$global:dgObjects.add_selectionChanged({
##Set-XamlProperty $global:dgObjects.Parent "btnDocument" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null) $false $true)
#$itemSelected = ($global:dgObjects.ItemsSource | Where IsSelected -eq $true).Count -ge 0 -or $global:dgObjects.SelectedItem
Set-XamlProperty $global:dgObjects.Parent "btnDocument" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null) $false $true)
})
$button.Add_Click({
@@ -57,6 +76,12 @@ function Invoke-ShowMainWindow
$global:spSubMenu.Children.Insert(0, $button)
}
function Invoke-EMSelectedItemsChanged
{
$hasSelectedItems = ($global:dgObjects.ItemsSource | Where IsSelected -eq $true) -or ($null -ne $global:dgObjects.SelectedItem)
Set-XamlProperty $global:dgObjects.Parent "btnDocument" "IsEnabled" $hasSelectedItems
}
function Invoke-GraphObjectsChanged
{
$btnDocument = $global:spSubMenu.Children | Where-Object { $_.Name -eq "btnDocument" }
@@ -87,6 +112,35 @@ function Invoke-ViewActivated
}
}
function Set-DocColumnHeaderLanguageId
{
param($columnName, $lngId)
if(-not $script:columnHeaders -or -not $lngId) { return }
if($script:columnHeaders.ContainsKey($columnName))
{
$script:columnHeaders[$columnName] = $lngId
}
else
{
$script:columnHeaders.Add($columnName, $lngId)
}
}
function Invoke-DocTranslateColumnHeader
{
param($columnName)
$lngText = ""
if($script:columnHeaders.ContainsKey($columnName))
{
$lngText = Get-LanguageString $script:columnHeaders[$columnName]
}
(?? $lngText $columnName)
}
function Add-OutputType
{
param($outputInfo)
@@ -588,27 +642,6 @@ function Add-ScopeTagStrings
}
}
function Get-AllEntityTypes
{
param($entityType, $xml, $hashTable)
if(-not $hashTable.ContainsKey($entityType))
{
$hashTable.Add($entityType, $xml.SelectSingleNode("//*[name()='EntityType' and @Name='$entityType']"))
}
$nodes = $xml.SelectNodes("//*[@BaseType='graph.$entityType']")
foreach($node in $nodes)
{
if($node.Abstract -ne "true")
{
$hashTable.Add($node.Name, $node)
}
Get-AllEntityTypes $node.Name $xml $hashTable
}
}
function Get-ObjectPlatformFromType
{
param($obj)

View File

@@ -0,0 +1,482 @@
function Get-ModuleVersion
{
'1.0.0'
}
function Invoke-InitializeModule
{
Add-OutputType ([PSCustomObject]@{
Name="Markdown (Experimental)"
Value="md"
#OutputOptions = (Add-MDOptionsControl)
#Activate = { Invoke-MDActivate @args }
PreProcess = { Invoke-MDPreProcessItems @args }
NewObjectGroup = { Invoke-MDNewObjectGroup @args }
Process = { Invoke-MDProcessItem @args }
PostProcess = { Invoke-MDPostProcessItems @args }
ProcessAllObjects = { Invoke-MDProcessAllObjects @args }
})
}
function Invoke-MDPreProcessItems
{
$script:mdStrings = [System.Text.StringBuilder]::new()
$script:sectionAnchors = @()
$script:totAnchors = @()
}
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")
$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)
{
$script:mdContent.AppendLine("[$($header.Name)](#$($header.Anchor))`n")
}
$script:mdContent.AppendLine("")
$mdText = $script:mdContent.ToString()
$mdText += $script:mdStrings.ToString()
$fileName = Expand-FileName "%MyDocuments%\%Organization%-%Date%.md"
try
{
$mdText | Out-File -FilePath $fileName -Force -Encoding utf8 -ErrorAction Stop
Write-Log "Markdown document $fileName saved successfully"
}
catch
{
Write-LogError "Failed to save Markdown file" $_.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-MDProcessItem
{
param($obj, $objectType, $documentedObj)
if(!$documentedObj -or !$obj -or !$objectType) { return }
$objName = Get-GraphObjectName $obj $objectType
Add-MDHeader $objName -Level 2 -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 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
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
if(($documentedObj.Assignments | measure).Count -gt 0)
{
$params = @{}
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.target.PSObject.Properties | Where Name -eq "deviceAndAppManagementAssignmentFilterType"))
{
$isFilterAssignment = $true
break
}
}
$properties = @("Group")
if($isFilterAssignment)
{
$properties += @("Filter","FilterMode")
}
$params.Add("AddCategories", $true)
}
Add-MDTableItems $obj $objectType $documentedObj.Assignments $properties "TableHeaders.assignments" @params
}
}
catch
{
Write-LogError "Failed to process object $objName" $_.Exception
}
}
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>")
$tableText.AppendLine("<tr>")
$columnHeaders = "|"
$columnChars = "|"
$columnCount = 0
foreach($prop in $properties)
{
$tableText.AppendLine("<td> $($prop.Split(".")[-1]) </td>")
$columnCount++
$columnHeaders += ((Invoke-DocTranslateColumnHeader ($prop.Split(".")[-1])) + "|")
$columnChars += "----|"
}
$tableText.AppendLine("</tr>")
#Add-MDText $columnHeaders
#Add-MDText $columnChars
$curCategory = ""
$curSubCategory = ""
$columnCategory = $null
$columnSubCategory = $null
foreach($itemObj in $items)
{
if($itemObj.Category -and $curCategory -ne $itemObj.Category -and $AddCategories -eq $true)
{
$tableText.AppendLine("<tr>")
$tableText.AppendLine("<td colspan=`"$($columnCount)`">`n`n**$((Set-MDText $itemObj.Category))**`n`n</td>")
$tableText.AppendLine("</tr>")
#$columnCategory = "|**" + (Set-MDText $itemObj.Category) + "**|"
#Add-MDText $columnCategory
$curCategory = $itemObj.Category
$curSubCategory = ""
}
if($itemObj.SubCategory -and $curSubCategory -ne $itemObj.SubCategory -and $AddSubcategories -eq $true)
{
$tableText.AppendLine("<tr>")
$tableText.AppendLine("<td colspan=`"$($columnCount)`">`n`n***$((Set-MDText $itemObj.SubCategory))***`n`n</td>")
$tableText.AppendLine("</tr>")
#$columnSubCategory = "|***" + (Set-MDText $itemObj.SubCategory) + "***|"
#Add-MDText $columnSubCategory
$curSubCategory = $itemObj.SubCategory
}
#$columnData = "|"
try
{
$tableText.AppendLine("<tr>")
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])"
}
$tableText.AppendLine("<td>$((Set-MDText "$($tmpObj.$propName)" -CodeBlock))</td>")
#$columnData += "$((Set-MDText "$($tmpObj.$propName)"))|"
}
catch
{
#$columnData += "|"
Write-LogError "Failed to add property value for $prop" $_.Exception
}
}
}
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($text, [switch]$CodeBlock)
if($CodeBlock -eq $true)
{
$trimText = $text.Trim()
if($trimText.StartsWith("<") -and $trimText.EndsWith(">"))
{
return ([Environment]::NewLine + [Environment]::NewLine + "``````xml" + [Environment]::NewLine + $text + [Environment]::NewLine + "``````" + [Environment]::NewLine + [Environment]::NewLine)
}
}
$text = $text.Replace("|", '`|')
$text = $text.Replace("*", '`*')
$text = $text.Replace("$", '`$')
$text = $text.Replace("`r`n", "<br />")
$text = $text.Replace("`n", "<br />")
$text
}
function Add-MDHeader
{
param($text, [int]$level = 1, [switch]$AddParagraph, [switch]$UseHTML, [switch]$ToT, [switch]$SkipTOC)
$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")
}
if($ToT -eq $true)
{
$script:totAnchors += [PSCustomObject]@{
Name = $text
Anchor = $sectionAnchor
}
}
elseif($SkipTOC -ne $true)
{
$script:sectionAnchors += [PSCustomObject]@{
Name = $text
Anchor = $sectionAnchor
}
}
if($AddParagraph -eq $true)
{
# Add new paragraph by default
$script:mdStrings.AppendLine("`n")
}
}
function Add-MDObjectSettings
{
param($obj, $objectType, $documentedObj)
if($obj."@OData.Type" -eq "#microsoft.graph.deviceManagementScript")
{
if($obj.ScriptContent)
{
$script:mdStrings.AppendLine("~~~powershell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.ScriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = "{1} - {0}" -f $obj.fileName,(Get-LanguageString "WindowsManagement.powerShellScriptObjectName")
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
}
if($obj."@OData.Type" -eq "#microsoft.graph.deviceShellScript")
{
if($obj.ScriptContent)
{
$script:mdStrings.AppendLine("~~~shell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.ScriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = "{1} - {0}" -f $obj.fileName,(Get-LanguageString "WindowsManagement.shellScriptObjectName")
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
}
elseif($obj."@OData.Type" -eq "#microsoft.graph.deviceHealthScript")
{
if($obj.detectionScriptContent)
{
$script:mdStrings.AppendLine("~~~powershell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.detectionScriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = Get-LanguageString "ProactiveRemediations.Create.Settings.DetectionScriptMultiLineTextBox.label"
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
if($obj.remediationScriptContent)
{
$script:mdStrings.AppendLine("~~~powershell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.remediationScriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = Get-LanguageString "ProactiveRemediations.Create.Settings.RemediationScriptMultiLineTextBox.label"
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
}
elseif($obj."@OData.Type" -eq "#microsoft.graph.win32LobApp")
{
foreach($rule in ($obj.requirementRules | Where { $_.'@OData.Type' -eq "#microsoft.graph.win32LobAppPowerShellScriptRequirement" } ))
{
$script:mdStrings.AppendLine("~~~powershell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.scriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = "{0} - {1}" -f @($obj.displayName, "Requirement script")
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
foreach($rule in ($obj.detectionRules | Where { $_.'@OData.Type' -eq "#microsoft.graph.win32LobAppPowerShellScriptDetection" } ))
{
$script:mdStrings.AppendLine("~~~powershell")
$script:mdStrings.AppendLine((Get-DocScriptContent $obj.scriptContent))
$script:mdStrings.AppendLine("~~~")
$caption = "{0} - {1}" -f @($obj.displayName,(Get-LanguageString "ProactiveRemediations.Create.Settings.DetectionScriptMultiLineTextBox.label"))
Add-MDHeader $caption -Level 6 -SkipTOC -AddParagraph
}
}
}

View File

@@ -3,7 +3,7 @@
#https://docs.microsoft.com/en-us/office/vba/api/overview/word
function Get-ModuleVersion
{
'1.0.6'
'1.0.7'
}
function Invoke-InitializeModule
@@ -163,7 +163,7 @@ function Invoke-WordPreProcessItems
}
catch
{
Write-LogError "Failed to create document based on tmeplate: $($global:txtWordDocumentTemplate.Text)" $_.Exception
Write-LogError "Failed to create document based on template: $($global:txtWordDocumentTemplate.Text)" $_.Exception
}
}
else
@@ -442,7 +442,7 @@ function Invoke-WordProcessItem
foreach($prop in $global:txtWordCustomProperties.Text.Split(","))
{
# This will add language support for custom colument (or replacing existing header)
# This will add language support for custom columens (or replacing existing header)
$propInfo = $prop.Split('=')
if(($propInfo | measure).Count -gt 1)
{

View File

@@ -11,7 +11,7 @@ This module is for the Endpoint Manager/Intune View. It manages Export/Import/Co
#>
function Get-ModuleVersion
{
'3.1.13'
'3.1.14'
}
function Invoke-InitializeModule
@@ -225,6 +225,7 @@ function Invoke-InitializeModule
Icon = "Branding"
SkipRemoveProperties = @('Id')
GroupId = "Azure"
SkipAddIDOnExport = $true
})
Add-ViewItem (New-Object PSObject -Property @{
@@ -722,9 +723,7 @@ function Set-EMViewPanel
Set-XamlProperty $panel "btnDelete" "Visibility" (?: ($allowDelete -eq $true) "Visible" "Collapsed")
$global:dgObjects.add_selectionChanged({
Set-XamlProperty $this.Parent "btnView" "IsEnabled" (?: ($null -eq $global:dgObjects.SelectedItem) $false $true)
Set-XamlProperty $this.Parent "btnCopy" "IsEnabled" (?: ($null -eq $global:dgObjects.SelectedItem) $false $true)
Set-XamlProperty $this.Parent "btnDelete" "IsEnabled" (?: ($null -eq $global:dgObjects.SelectedItem -and $global:curObjectType.AllowDelete -ne $false) $false $true)
Invoke-ModuleFunction "Invoke-EMSelectedItemsChanged"
})
# ToDo: Move this to the view object
@@ -761,11 +760,39 @@ function Set-EMViewPanel
Write-Status ""
})
}
$global:btnLoadAllPages.add_click({
Write-Status "Loading $($global:curObjectType.Title) objects"
$graphObjects = @(Get-GraphObjects -property $global:curObjectType.ViewProperties -objectType $global:curObjectType -AllPages)
$graphObjects | ForEach-Object { $global:dgObjects.ItemsSource.AddNewItem($_) | Out-Null }
$global:dgObjects.ItemsSource.CommitNew()
Set-GraphPagesButtonStatus
Invoke-FilterBoxChanged $global:txtFilter -ForceUpdate
Write-Status ""
})
$global:btnLoadNextPage.add_click({
Write-Status "Loading $($global:curObjectType.Title) objects"
$graphObjects = @(Get-GraphObjects -property $global:curObjectType.ViewProperties -objectType $global:curObjectType -SinglePage)
$graphObjects | ForEach-Object { $global:dgObjects.ItemsSource.AddNewItem($_) | Out-Null }
$global:dgObjects.ItemsSource.CommitNew()
Set-GraphPagesButtonStatus
Invoke-FilterBoxChanged $global:txtFilter -ForceUpdate
Write-Status ""
})
}
function Invoke-EMSelectedItemsChanged
{
$hasSelectedItems = ($global:dgObjects.ItemsSource | Where IsSelected -eq $true) -or ($null -ne $global:dgObjects.SelectedItem)
Set-XamlProperty $global:dgObjects.Parent "btnView" "IsEnabled" $hasSelectedItems #(?: ($null -eq ($global:dgObjects.SelectedItem)) $false $true)
Set-XamlProperty $global:dgObjects.Parent "btnCopy" "IsEnabled" $hasSelectedItems #(?: ($null -eq $global:dgObjects.SelectedItem) $false $true)
Set-XamlProperty $global:dgObjects.Parent "btnDelete" "IsEnabled" $hasSelectedItems #(?: ($null -eq $global:dgObjects.SelectedItem -and $global:curObjectType.AllowDelete -ne $false) $false $true)
}
function Invoke-FilterBoxChanged
{
param($txtBox)
param($txtBox,[switch]$ForceUpdate)
$filter = $null
@@ -775,10 +802,17 @@ function Invoke-FilterBoxChanged
$txtBox.Tag = 1
$txtBox.Text = "Filter"
$txtBox.Foreground="Lightgray"
}
elseif($ForceUpdate -eq $true)
{
$dgObjects.ItemsSource.Filter = $dgObjects.ItemsSource.Filter
}
elseif($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false)
{
}
else
{
if($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false) { return }
$txtBox.FontStyle = "Normal"
$txtBox.Tag = $null
$txtBox.Foreground="Black"
@@ -800,11 +834,9 @@ function Invoke-FilterBoxChanged
}
}
if($dgObjects.ItemsSource -is [System.Windows.Data.ListCollectionView])
if($dgObjects.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
$dgObjects.ItemsSource.Filter = $filter
$dgObjects.ItemsSource.Refresh()
}
$allObjectsCount = 0
@@ -869,9 +901,15 @@ function Start-PostExportEndpointSecurity
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
}
$settings = Invoke-GraphRequest -Url "$($objectType.API)/$($obj.id)/settings"
$settingsJson = "{ `"settings`": $((ConvertTo-Json $settings.value -Depth 20 ))`n}"
$fileName = "$path\$((Remove-InvalidFileNameChars (Get-GraphObjectName $obj $objectType)))_Settings.json"
$fileName = "$path\$((Remove-InvalidFileNameChars $fileName))_Settings.json"
$settingsJson | Out-File -LiteralPath $fileName -Force
}
@@ -1142,8 +1180,8 @@ function Start-PostGetIntuneBranding
foreach($imgType in @("themeColorLogo","lightBackgroundLogo","landingPageCustomizedImage"))
{
Write-LogDebug "Get $imgType for $($obj.profileName)"
$imgJson = Invoke-GraphRequest -Url "$($objectType.API)/$($obj.Id)/$imgType"
Write-LogDebug "Get $imgType for $($obj.Object.profileName)"
$imgJson = Invoke-GraphRequest -Url "$($objectType.API)/$($obj.Object.Id)/$imgType"
if($imgJson.Value)
{
$obj.Object.$imgType = $imgJson
@@ -1793,9 +1831,23 @@ function Start-PostExportAdministrativeTemplate
{
param($obj, $objectType, $path)
$fileName = (Get-GraphObjectName $obj $objectType)
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
}
# Collect and save all the settings of the Administrative Templates profile
$settings = Get-GPOObjectSettings $obj
$fileName = "$path\$((Remove-InvalidFileNameChars (Get-GraphObjectName $obj $objectType)))_Settings.json"
if($obj.definitionValues)
{
$settings = $obj.definitionValues
}
else
{
$settings = Get-GPOObjectSettings $obj
}
$fileName = "$path\$((Remove-InvalidFileNameChars $fileName))_Settings.json"
ConvertTo-Json $settings -Depth 20 | Out-File -LiteralPath $fileName -Force
}
@@ -1858,7 +1910,7 @@ function Start-PostGetAdministrativeTemplate
$obj.Object | Add-Member Noteproperty -Name "definitionValues" -Value $definitionValues -Force
}
<#
# Leave for now. This only loads the configured defenition values and not the values specified.
# Leave for now. This only loads the configured definition values and not the values specified.
# That would require enumerating each definition value which takes time.
$definitionValues = (Invoke-GraphRequest "deviceManagement/groupPolicyConfigurations('$($obj.Id)')/definitionValues?`$expand=definition(`$select=id,classType,displayName,policyType,groupPolicyCategoryId)" -ODataMetadata "minimal").value
@@ -2025,8 +2077,21 @@ function Start-PostExportRoleDefinitions
{
param($obj, $objectType, $path)
$fileName = "$path\$((Remove-InvalidFileNameChars (Get-GraphObjectName $obj $objectType))).json"
$tmpObj = Get-Content -LiteralPath $fileName | ConvertFrom-Json
$fileName = (Get-GraphObjectName $obj $objectType)
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
}
$tmpObj = $null
$fileName = "$path\$((Remove-InvalidFileNameChars $fileName)).json"
if([IO.File]::Exists($fileName))
{
$tmpObj = Get-Content -LiteralPath $fileName | ConvertFrom-Json
}
else
{
Write-Log "File not found: $fileName. Could not get role assignments" 3
}
if(($tmpObj.RoleAssignments | measure).Count -gt 0)
{
@@ -2397,10 +2462,15 @@ function Add-EMAssignmentsToExportFile
{
param($obj, $objectType, $path, $Url = "")
$fileName = "$path\$((Remove-InvalidFileNameChars (Get-GraphObjectName $obj $objectType))).json"
$fileName = (Get-GraphObjectName $obj $objectType)
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
{
$fileName = ($fileName + "_" + $obj.Id)
}
$fileName = "$path\$((Remove-InvalidFileNameChars $fileName)).json"
if([IO.File]::Exists($fileName) -eq $false)
{
Write-Log "File not found: $fileName. Could not add assignments" 3
Write-Log "File not found: $fileName. Could not add assignments to file" 3
return
}
$tmpObj = Get-Content -LiteralPath $fileName | ConvertFrom-Json

View File

@@ -10,7 +10,7 @@ This module manages Application objects in Intune e.g. uploading application fil
#>
function Get-ModuleVersion
{
'3.1.1'
'3.1.2'
}
#########################################################################################
@@ -42,6 +42,12 @@ function Get-MSIFileInformation
$values = @{}
if(-not $MSIFile) { return }
$fi = [IO.FileInfo]$MSIFile
if($fi.Extension -ne ".msi") { return }
try
{
$wiObj = New-Object -ComObject WindowsInstaller.Installer

View File

@@ -9,7 +9,7 @@ Module for listing Intune assignments
#>
function Get-ModuleVersion
{
'1.0.2'
'1.0.3'
}
function Invoke-InitializeModule
@@ -231,10 +231,13 @@ function Invoke-IntueAssignmentFilterBoxChanged
$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
{
if($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false) { return }
$txtBox.FontStyle = "Normal"
$txtBox.Tag = $null
$txtBox.Foreground="Black"
@@ -245,15 +248,15 @@ function Invoke-IntueAssignmentFilterBoxChanged
$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) )
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])
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()
#$dgObject.ItemsSource.Refresh()
}
}

View File

@@ -22,7 +22,7 @@ $global:EMToolsViewObject = $null
function Get-ModuleVersion
{
'1.0.2'
'1.0.3'
}
function Invoke-InitializeModule
@@ -890,10 +890,13 @@ function Invoke-ADMXFilterPolicies
$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
{
if($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false) { return }
$txtBox.FontStyle = "Normal"
$txtBox.Tag = $null
$txtBox.Foreground="Black"
@@ -908,11 +911,11 @@ function Invoke-ADMXFilterPolicies
}
}
if($global:dgADMXSettings.ItemsSource -is [System.Windows.Data.ListCollectionView])
if($global:dgADMXSettings.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
$global:dgADMXSettings.ItemsSource.Filter = $filter
$global:dgADMXSettings.ItemsSource.Refresh()
#$global:dgADMXSettings.ItemsSource.Refresh()
}
}

View File

@@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res
#>
function Get-ModuleVersion
{
'3.3.1'
'3.3.2'
}
$global:msalAuthenticator = $null
@@ -121,6 +121,14 @@ function Invoke-InitializeModule
DefaultValue = "gcc"
}) "MSAL"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Sort Account List"
Key = "SortAccountList"
Type = "Boolean"
DefaultValue = $false
Description = "Sort the list of cached accounts based on user name. Updated at restart or account change"
}) "MSAL"
Add-MSALPrereq
#$script:MSALDLLMissing = $true #!!!!
@@ -1389,13 +1397,21 @@ function Get-MSALProfileEllipse
[System.Windows.Controls.Canvas]::SetTop($obj,($point.Y + $obj.Tag.ActualHeight))
})
$otherLogins = $global:grdProfileInfo.FindName("grdAccountsAndTenants")
$otherLogins = $global:grdProfileInfo.FindName("grdCachedAccounts")
#########################################################################################################
### Add cached users
#########################################################################################################
if((Get-SettingValue "SortAccountList") -eq $true)
{
$accounts = $global:MSALAccounts | Sort -Property Username
}
else
{
$accounts = $global:MSALAccounts
}
foreach($account in $global:MSALAccounts)
foreach($account in $accounts)
{
# Skip current logged on user
if($global:MSALToken.Account.Username -eq $Account.Username) { continue }
@@ -1520,8 +1536,12 @@ function Get-MSALProfileEllipse
Write-Status ""
})
$otherLogins = $global:grdProfileInfo.FindName("grdLoginAccount")
Add-GridObject $otherLogins $lnkButton
$otherLogins = $global:grdProfileInfo.FindName("grdTenantAccounts")
if(($script:AccessableTenants | measure).Count -gt 1)
{
#########################################################################################################

View File

@@ -10,7 +10,7 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob
#>
function Get-ModuleVersion
{
'3.1.9'
'3.1.10'
}
$global:MSGraphGlobalApps = @(
@@ -159,6 +159,23 @@ function Invoke-InitializeModule
DefaultValue = $false
Description = "This will add object ID to the export file to support objects with the same name e.g. ObjectName_ObjectId.json"
}) "ImportExport"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Use Batch API (Preview)"
Key = "UseBatchAPI"
Type = "Boolean"
DefaultValue = $false
Description = "This will use batch API to call up to extport 20 objects on each API call"
}) "ImportExport"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Refresh Objects after copy"
Key = "RefreshObjectsAfterCopy"
Type = "Boolean"
DefaultValue = $true
Description = "This will refresh all objects when after a copy. If this is disabled, the list must be refreshed manually to see the new objects. Default is true"
}) "ImportExport"
}
function Get-GraphAppInfo
@@ -240,6 +257,12 @@ function Invoke-GraphRequest
[switch]
$AllPages,
[int]
$PageSize = -1,
[switch]
$Batch,
[switch]
$NoError
)
@@ -313,12 +336,8 @@ function Invoke-GraphRequest
$Url = $Url -replace "%OrganizationId%", $global:Organization.Id
}
<#
if($AllPages)
if($PageSize -gt 0 -and $url.IndexOf("`$top=") -eq -1)
{
# Code to test paging - Force each page to size specified in top parameter below
# Kept for reference
if(($url.IndexOf('?')) -eq -1)
{
$url = "$($url.Trim())?"
@@ -327,9 +346,8 @@ function Invoke-GraphRequest
{
$url = "$($url.Trim())&"
}
$url = "$($url.Trim())`$top=20"
$url = "$($url.Trim())`$top=$($PageSize)"
}
#>
$ret = $null
try
@@ -390,13 +408,15 @@ function Get-GraphObjects
$exclude,
$SortProperty = "displayName",
$objectType,
[string]
$select,
[switch]
$SinglePage,
[switch]
$AllPages,
[switch]
$SingleObject)
$objects = @()
if($property -isnot [Object[]]) { $property = @('displayName', 'description', 'id')}
$params = @{}
if($objectType.ODataMetadata)
{
@@ -420,12 +440,32 @@ function Get-GraphObjects
}
}
if($SingleObject -ne $true)
if(($url.IndexOf("`$select=")) -eq -1 -and $select)
{
$url += (?: (($url.IndexOf('?')) -eq -1) "?" "&")
$url += "`$select=$select"
}
if($SinglePage -eq $true)
{
#Use default page size or use below for a specific page size for testing
#$params.Add("pageSize",100)
}
elseif($SingleObject -ne $true -and $SinglePage -ne $true)
{
$params.Add('AllPages',$true)
}
if($script:nextGraphPage -and ($SinglePage -eq $true -or $AllPages -eq $true))
{
$url = $script:nextGraphPage
}
$graphObjects = Invoke-GraphRequest -Url $url @params
if($SinglePage -eq $true -or $AllPages -eq $true)
{
$script:nextGraphPage = $graphObjects.'@odata.nextLink'
}
if($SingleObject -ne $true -and $objectType.PostListCommand)
{
@@ -441,6 +481,56 @@ function Get-GraphObjects
$retObjects = $graphObjects
}
return (Add-GraphObectProperties $retObjects $objectType $property $SortProperty)
$objects = @()
foreach($graphObject in $retObjects)
{
$params = @{}
if($property) { $params.Add("Property", $property) }
if($exclude) { $params.Add("ExcludeProperty", $exclude) }
foreach($objTmp in ($graphObject | Select-Object @params))
{
$objTmp | Add-Member -NotePropertyName "IsSelected" -NotePropertyValue $false
$objTmp | Add-Member -NotePropertyName "Object" -NotePropertyValue $graphObject
$objTmp | Add-Member -NotePropertyName "ObjectType" -NotePropertyValue $objectType
$objects += $objTmp
}
}
$property = "IsSelected",$property
if($objects.Count -gt 0 -and $SortProperty -and ($objects[0] | GM -MemberType NoteProperty -Name $SortProperty))
{
$objects = $objects | sort -Property $SortProperty
}
$objects
}
function Add-GraphObectProperties
{
param($graphObjects,
$objectType,
[Array]
$property = $null,
[Array]
$exclude,
$SortProperty = "displayName")
if($property -isnot [Object[]]) { $property = @('displayName', 'description', 'id')}
$objects = @()
if($graphObjects -and ($graphObjects | GM -Name Value -MemberType NoteProperty))
{
$retObjects = $graphObjects.Value
}
else
{
$retObjects = $graphObjects
}
foreach($graphObject in $retObjects)
{
$params = @{}
@@ -460,6 +550,7 @@ function Get-GraphObjects
{
$objects = $objects | sort -Property $SortProperty
}
$objects
}
@@ -505,7 +596,9 @@ function Show-GraphObjects
$global:grdTitle.Visibility = "Visible"
}
$graphObjects = @(Get-GraphObjects -property $global:curObjectType.ViewProperties -objectType $global:curObjectType)
$script:nextGraphPage = $null
$graphObjects = @(Get-GraphObjects -property $global:curObjectType.ViewProperties -objectType $global:curObjectType -SinglePage)
$dgObjects.AutoGenerateColumns = $false
$dgObjects.Columns.Clear()
@@ -526,6 +619,7 @@ function Show-GraphObjects
$item.IsSelected = $this.IsChecked
}
$global:dgObjects.Items.Refresh()
Invoke-ModuleFunction "Invoke-EMSelectedItemsChanged"
})
}
@@ -585,6 +679,7 @@ function Show-GraphObjects
$dgObjects.ItemsSource = $null
}
# Show/Hide buttons based on object type
foreach($ctrl in $spSubMenu.Children)
{
@@ -604,6 +699,16 @@ function Show-GraphObjects
$ctrl.Visibility = "Collapsed"
}
}
Set-GraphPagesButtonStatus
}
function Set-GraphPagesButtonStatus
{
$global:btnLoadAllPages.Visibility = (?: ($script:nextGraphPage) "Visible" "Collapsed")
$global:btnLoadNextPage.Visibility = (?: ($script:nextGraphPage) "Visible" "Collapsed")
$global:btnLoadAllPages.Tag = $script:nextGraphPage
$global:btnLoadNextPage.Tag = $script:nextGraphPage
}
function Clear-GraphObjects
@@ -620,7 +725,7 @@ function Clear-GraphObjects
function Get-GraphObject
{
param($obj, $objectType, [switch]$SkipAssignments)
param($obj, $objectType, [switch]$SkipAssignments, [switch]$GetAPI)
Write-Status "Loading $((Get-GraphObjectName $obj $objectType))"
@@ -696,6 +801,16 @@ function Get-GraphObject
$api = ($api + ($expand -join ","))
}
if($global:Organization.Id)
{
$api = $api -replace "%OrganizationId%", $global:Organization.Id
}
if($GetAPI -eq $true)
{
return $api
}
$objInfo = Get-GraphObjects -Url $api -property $objectType.ViewProperties -objectType $objectType -SingleObject
if($objInfo -and $objectType.PostGetCommand)
@@ -959,18 +1074,38 @@ function Show-GraphBulkExportForm
{
$folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:exportForm "txtExportPath" "Text") (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked")
Write-Status "Get a list of all $($item.ObjectType.Title) objects" -SkipLog -Force
$objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
foreach($obj in $objects)
if((Get-SettingValue "UseBatchAPI") -eq $true)
{
$objName = Get-GraphObjectName $obj.Object $obj.ObjectType
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
# Use batch to get details of each object
$batchObjects = Get-GraphBatchObjects $objects $txtNameFilter
$i = 1
$total = ($batchObjects | measure).Count
foreach($batchResult in $batchObjects)
{
continue
$objName = Get-GraphObjectName $batchResult.Object $batchResult.ObjectType
Write-Status "Export $($item.Title): $objName ($($i)/$($total))" -Force
Export-GraphObject $batchResult.Object $batchResult.ObjectType $folder -IsFullObject
$i++
}
}
else
{
foreach($obj in $objects)
{
# Export objects one by one
$objName = Get-GraphObjectName $obj.Object $obj.ObjectType
Write-Status "Export $($item.Title): $objName" -Force
Export-GraphObject $obj.Object $item.ObjectType $folder
if($txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
{
continue
}
Write-Status "Export $($item.Title): $objName" -Force
Export-GraphObject $obj.Object $item.ObjectType $folder
}
}
Save-Setting "" "LastUsedFullPath" $folder
}
@@ -2288,13 +2423,21 @@ function Export-GraphObject
{
param($objToExport,
$objectType,
$exportFolder)
$exportFolder,
[switch]$IsFullObject)
if(-not $exportFolder) { return }
Write-Status "Export $((Get-GraphObjectName $objToExport $objectType))"
$obj = Get-GraphExportObject $objToExport $objectType
if($IsFullObject -eq $true)
{
$obj = $objToExport
}
else
{
$obj = Get-GraphExportObject $objToExport $objectType
}
if(-not $obj)
{
@@ -2315,7 +2458,7 @@ function Export-GraphObject
}
$fileName = Get-GraphObjectName $obj $objectType
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id)
if((Get-SettingValue "AddIDToExportFile") -eq $true -and $obj.Id -and $objectType.SkipAddIDOnExport -ne $true)
{
$fileName = ($fileName + "_" + $obj.Id)
}
@@ -2335,6 +2478,83 @@ function Export-GraphObject
}
}
function Get-GraphBatchObjects
{
param($objects, $txtNameFilter)
$curBatch = 1
$batchArr = @()
$batchResults = @()
$batchTotal = 0
$objectType = $null
foreach($obj in $objects)
{
$objectType = $obj.ObjectType
$objName = Get-GraphObjectName $obj.Object $obj.ObjectType
if($objName -and $txtNameFilter -and $objName -notmatch [RegEx]::Escape($txtNameFilter))
{
$batchTotal++
}
else
{
$ometadata = ?? $obj.ObjectType.ODataMetadata "Full"
$batchArr += [PSCustomObject]@{
id = ($batchArr.Count + 1)
method = "GET"
url = (Get-GraphObject $obj.Object $obj.ObjectType -GetAPI)
headers = @{"Accept"="application/json;odata.metadata=$ometadata"}
}
}
if($batchArr.Count -eq 20 -or ($batchTotal + $batchArr.Count -eq $objects.Count))
{
$batchObj = [PSCustomObject]@{
requests = $batchArr
}
Write-Status "Get batch $curBatch $($obj.ObjectType.Title)" -Force
$batchTotal += $batchArr.Count
$json = $batchObj | ConvertTo-Json -Depth 10
$tmpResults = Invoke-GraphRequest -Url "`$batch" -Content $json -HttpMethod "POST" -Batch #-Url $api -property $obj.ObjectType.ViewProperties -objectType $obj.ObjectType -
$curResp = 1
foreach($batchResult in ($tmpResults.responses | Sort -Property Id))
{
if($batchResult.Status -ne "200" -or -not $batchResult.body)
{
$reqObj = $batchObj.requests | where id -eq $batchResult.Id
Write-Log "Batch result $($batchResult.Status) for URL $($reqObj.URL). Skipping..." 2
continue
}
$batchResults += $batchResult.body
$curResp++
}
$curBatch++
$batchArr = @()
}
}
if($objectType -and $batchResults.Count -gt 0)
{
$batchResultsTmp = $batchResults
$batchResults = Add-GraphObectProperties $batchResultsTmp $objectType -property $objectType.ViewProperties
$curObj = 1
foreach($obj in $batchResults)
{
if($obj.Object -and $obj.ObjectType.PostGetCommand)
{
Write-Status "Get full info - $((Get-GraphObjectName $obj.Object $obj.ObjectType)) ($($curObj)/$(@($batchResults).Count))" -Force
& $obj.ObjectType.PostGetCommand $obj $obj.ObjectType
}
$curObj++
}
}
$batchResults
}
function Get-GraphExportObject
{
param($obj, $objectType)
@@ -2527,7 +2747,10 @@ function Copy-GraphObject
{
if((& $global:curObjectType.PreCopyCommand $exportObj $global:curObjectType $ret))
{
Show-GraphObjects
if((Get-SettingValue "RefreshObjectsAfterCopy") -eq $true)
{
Show-GraphObjects
}
Write-Status ""
return
}
@@ -2547,7 +2770,10 @@ function Copy-GraphObject
{
& $global:curObjectType.PostCopyCommand $exportObj $newObj $global:curObjectType
}
Show-GraphObjects
if((Get-SettingValue "RefreshObjectsAfterCopy") -eq $true)
{
Show-GraphObjects
}
}
else
{
@@ -2675,3 +2901,74 @@ function Add-GraphBulkMenu
$mnuMain.Items.Insert(1,$menuItem) | Out-Null
}
function Get-GraphAllEntityTypes
{
param($entityType, $xml, $hashTable)
if(-not $hashTable.ContainsKey($entityType))
{
$hashTable.Add($entityType, $xml.SelectSingleNode("//*[name()='EntityType' and @Name='$entityType']"))
}
$nodes = $xml.SelectNodes("//*[@BaseType='graph.$entityType']")
foreach($node in $nodes)
{
if($node.Abstract -ne "true")
{
$hashTable.Add($node.Name, $node)
}
Get-GraphAllEntityTypes $node.Name $xml $hashTable
}
}
function Get-GraphEntityTypeObject
{
param($entityType, $xml, $skipProperties = @())
$props = Get-GraphEntityTypeProperties $entityType $xml
if(-not $props) { return }
$obj = [PSCustomObject]@{
}
foreach($prop in $props)
{
if($prop.Name -in $skipProperties) { continue }
$obj | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $null
}
$obj
}
function Get-GraphEntityTypeProperties
{
param($entityType, $xml)
$tmpEntity = $xml.SelectSingleNode("//*[name()='EntityType' and @Name='$entityType']")
if(-not $tmpEntity) { return }
$entities = @()
$entities += $tmpEntity
while($tmpEntity.BaseType)
{
$baseType = $tmpEntity.BaseType.Split('.')[-1]
$tmpEntity = $xml.SelectSingleNode("//*[name()='EntityType' and @Name='$baseType']")
if($tmpEntity)
{
$entities += $tmpEntity
}
}
$properties = @()
[array]::Reverse($entities)
foreach($enitiy in $entities)
{
$properties += $enitiy.SelectNodes("*[name()='Property']")
}
$properties
}

View File

@@ -275,7 +275,8 @@ When multi tenant settings is Enabled/Disabled, the Profile Info is not updated
The *List Applications* API might not list an imported app immediately after the import. Click *Refresh* to reload the application objects.
When using the filter box to search for items, the checkbox must be clicked twice to select an item.
~~When using the filter box to search for items, the checkbox must be clicked twice to select an item.~~</br />
Issue fixed in 3.3.2
Logout will only clear the token from cache and not from the browser e.g. if login is triggered after a logout, the user will still be listed in the 'Select user' dialog.

View File

@@ -1,5 +1,44 @@
# Release Notes
## 3.3.2 - 2021-12-14
**New features**
- Markdown support for documentation (Experimental)
This will create a MD document in the Documents folder.
**Note:** This is not working 100% at the moment. The script will create a MD document but it might be too large if all objects in the environment are documented.
Also note that HTML tables are used so that code can be documented as code blocks. This must be supported by the MD Viewer. The *Markdown Viewer* extension in Chrome was used during testing.
Please report any suggestions to the issue.<br />
This is based on [Issue 35](https://github.com/Micke-K/IntuneManagement/issues/35)
- Added support for batched export
This will use batch API to request full info for up to 20 objects per batch to reduce export time
This can be enabled in setting
- Added support for scrolling cached users and guest accounts in the profile info<br />
This can be enabled in settings
- Added support for sorting cached users<br />
This can be enabled in settings
**Fixes**
- Paged return of objects<br />
Only first page of objects will be loaded by default.
Additional pages can be loaded with **Load More** or all available objects can be loaded with **Load All**.<br />
This is based on [Issue 28](https://github.com/Micke-K/IntuneManagement/issues/28)
- Fixed an issue where a checkbox had to be clicked twice to be checked when the list was filtered <br />
This is based on a known issue
- Fixed an issue where buttons were not enabled when **Select All** was checked<br />
This is based on [Issue 36](https://github.com/Micke-K/IntuneManagement/issues/36)
- Fixed an issue when adding object ID to the file name during export
The separate settings file was not exported with the ID in the name which could cause issues during import
## 3.3.1 (Beta) - 2021-10-28
This is a **BETA** release. It contains core changes for Authentication and Settings management. Please report any issues [here](https://github.com/Micke-K/IntuneManagement/issues).

View File

@@ -14,7 +14,13 @@
SelectionUnit="FullRow"
CanUserAddRows="False"
Grid.Column="1"
Grid.Row="1" />
Grid.Row="1">
<DataGrid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
</DataGrid.Resources>
</DataGrid>
<Grid Name="grdTitle" Visibility="Collapsed" >
<Grid.ColumnDefinitions>
@@ -22,13 +28,17 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Background="{DynamicResource TitleBackgroundColor}" Margin="0,0,0,5" >
<ContentControl Name="ccIcon" Width="24" Height="24" Margin="5,0,0,0" />
</Border>
<TextBlock Name="txtFormTitle" Text="" Grid.Column="1" Grid.ColumnSpan="3" FontWeight="Bold" Padding="5" Margin="0,0,0,5" Background="{DynamicResource TitleBackgroundColor}" />
<TextBox Name="txtFilter" Grid.Column="2" MinWidth="200" MaxHeight="20" Margin="0,0,5,3" />
<TextBlock Name="txtFormTitle" Text="" Grid.Column="1" Grid.ColumnSpan="5" FontWeight="Bold" Padding="5" Margin="0,0,0,5" Background="{DynamicResource TitleBackgroundColor}" />
<Button Name="btnLoadAllPages" Grid.Column="2" Content="Load All" Width='100' Margin="0,5,5,7" Visibility="Collapsed" />
<Button Name="btnLoadNextPage" Grid.Column="3" Content="Load More" Width='100' Margin="0,5,5,7" Visibility="Collapsed" />
<TextBox Name="txtFilter" Grid.Column="4" MinWidth="200" MaxHeight="20" Margin="0,0,5,3" />
<!--
<Grid Grid.Column="2" MinWidth="200" MaxHeight="20">
<TextBlock Text="Filter" Background="White" />

View File

@@ -11,6 +11,8 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -46,10 +48,27 @@
<Button Margin="5" Name="lnkForceRefresh" ToolTip="Force a refresh of the token e.g. after being added to a new role" Content="Refresh" Cursor="Hand" Style="{DynamicResource LinkButton}" />
</StackPanel>
<Grid Grid.Row="8" Name="grdAccountsAndTenants" Grid.ColumnSpan="2" Margin="5,0,5,0" HorizontalAlignment="Stretch">
<ScrollViewer Grid.Row="8" Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MaxHeight="200">
<Grid Name="grdCachedAccounts" Margin="5,0,5,2" HorizontalAlignment="Stretch" VerticalAlignment="Top" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</ScrollViewer>
<Grid Name="grdLoginAccount" Grid.Row="9" Grid.ColumnSpan="2" Margin="5,0,5,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<ScrollViewer Grid.Row="10" Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MaxHeight="200">
<Grid Name="grdTenantAccounts" Margin="5,0,5,2" HorizontalAlignment="Stretch" VerticalAlignment="Top" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</ScrollViewer>
</Grid>
</Border>