mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
1084 lines
45 KiB
PowerShell
1084 lines
45 KiB
PowerShell
Add-AppEvent "IntuneManagerUISelectedMenuItemChanged"
|
|
Add-AppEvent "IntuneManagerUISelectedItemChanged"
|
|
|
|
Add-AppEventHandler "AppInitialized" "Invoke-IntuneUIAppInitialized"
|
|
Add-AppEventHandler "SettingsUpdated" "Invoke-IntuneUIEventSettingsUpdated"
|
|
|
|
function Get-IntuneViewItems
|
|
{
|
|
[OutputType([PSCustomObject[]])]
|
|
param()
|
|
|
|
# ToDo: Fix support for PolicyGroup OR PolicyType
|
|
|
|
$viewType = Get-SettingValue "ObjectViewType" # "Type"
|
|
if($viewType -eq "Type") {
|
|
$viewItems = $script:IntuneTypes | Sort-Object -Property Title
|
|
}
|
|
else {
|
|
$viewItems = $script:IntuneGroups | Sort-Object -Property Title
|
|
}
|
|
|
|
# Read-only "Intune Info" group (Baseline Templates, Tenant Settings, etc.) is
|
|
# opt-in: hidden unless enabled via the gear menu's "Show read-only Info". Drop
|
|
# the group (Group view) or its member types (API view).
|
|
if((Get-SettingStoreValue "IntuneManager" "ShowIntuneInfo" "false") -ne "true") {
|
|
if($viewType -eq "Type") {
|
|
$viewItems = @($viewItems | Where-Object { -not ($_.PolicyGroup -and $_.PolicyGroup.Id -eq "IntuneInfo") })
|
|
}
|
|
else {
|
|
$viewItems = @($viewItems | Where-Object { $_.Id -ne "IntuneInfo" })
|
|
}
|
|
}
|
|
|
|
# Stamp AccessType / AccessInfo from the token's granted scopes so the
|
|
# ListBox triggers in MainWindow.xaml can colour restricted rows (orange =
|
|
# read-only, red = no access). Works for both view modes: groups aggregate
|
|
# their member types. No-op when there is no token to diff against.
|
|
Update-IntuneAccessLevels
|
|
|
|
if((Get-SettingValue "HideNoAccess"))
|
|
{
|
|
# Drop rows the token cannot use at all. Groups aggregate to None only
|
|
# when every member type is unusable, so a partially usable group is
|
|
# never hidden - it stays visible and orange.
|
|
$viewItems = @($viewItems | Where-Object { $_.AccessType -ne [APIAccess]::None })
|
|
}
|
|
|
|
# MenuLabel is the bound display string for each menu row (template uses
|
|
# `Text="{Binding MenuLabel}"`). Default to Title; Update-IntuneViewItemCounts
|
|
# later overrides with "Title (N)" when the gear's "Show item counts"
|
|
# toggle is on. Always reset to Title here so a view-mode switch can't
|
|
# leave a stale "(N)" attached when counts are off.
|
|
$showCounts = (Get-SettingStoreValue "IntuneManager" "ShowMenuItemCounts" "false") -eq "true"
|
|
foreach($item in $viewItems) {
|
|
$item | Add-Member -NotePropertyName "MenuLabel" -NotePropertyValue $item.Title -Force
|
|
}
|
|
|
|
# Defer to Show-ViewMenu's post-bind hook to populate counts (it has access
|
|
# to $script:lstMenuItems after the bind happens). $showCounts only here
|
|
# so the variable is referenced — actual count population happens later.
|
|
$script:_intuneShowItemCounts = $showCounts
|
|
|
|
return ($viewItems)
|
|
}
|
|
|
|
# Batched type-count fetcher. Runs once per Refresh / first count-enable;
|
|
# cached results power both API view (1 count per row) and Group view (sum
|
|
# of child-type counts per group row). Skips silently if there's no signed-
|
|
# in token — counts will populate the next time the menu rebuilds.
|
|
function Update-IntuneViewItemCounts
|
|
{
|
|
param([switch]$Force)
|
|
|
|
if(-not $script:lstMenuItems -or -not $script:lstMenuItems.ItemsSource) { return }
|
|
|
|
$tokenId = $null
|
|
try { $tokenId = Get-DefaultTokenId } catch { }
|
|
if($null -eq $tokenId) {
|
|
Write-LogDebug "Update-IntuneViewItemCounts: no token id available - skipping"
|
|
return
|
|
}
|
|
|
|
# Fetch ALL type counts (not just the visible rows) so Group view can roll
|
|
# up without a second pass. The fetch + session cache live in
|
|
# Internal/IntuneManager.ps1 (Get-IntuneTypeCounts) and are shared with the
|
|
# Avalonia backend; -Force invalidates.
|
|
[void](Get-IntuneTypeCounts -Force:$Force -TokenId $tokenId)
|
|
|
|
# Project counts onto the currently-bound MenuLabels. Type rows: direct
|
|
# lookup. Group rows: sum of every child type's count (skips types with
|
|
# no count so a single 400 doesn't void the whole group).
|
|
foreach($it in $script:lstMenuItems.ItemsSource) {
|
|
$count = $null
|
|
if($it -is [IntunePolicyTypeBase]) {
|
|
if($script:_intuneTypeCounts.ContainsKey($it.Id)) { $count = $script:_intuneTypeCounts[$it.Id] }
|
|
}
|
|
elseif($it -is [IntunePolicyGroupBase]) {
|
|
$sum = 0; $hasAny = $false
|
|
foreach($child in @($it.PolicyTypes)) {
|
|
if($child -and $script:_intuneTypeCounts.ContainsKey($child.Id)) {
|
|
$sum += $script:_intuneTypeCounts[$child.Id]; $hasAny = $true
|
|
}
|
|
}
|
|
if($hasAny) { $count = $sum }
|
|
}
|
|
$label = if($null -ne $count) { "$($it.Title) ($count)" } else { $it.Title }
|
|
$it | Add-Member -NotePropertyName "MenuLabel" -NotePropertyValue $label -Force
|
|
}
|
|
try { $script:lstMenuItems.Items.Refresh() } catch { }
|
|
}
|
|
|
|
function Get-IntuneManagementViewPanel
|
|
{
|
|
$panel = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\IntuneManagerPanel.xaml"))
|
|
|
|
$script:dgIntuneManagerObjects = $panel.FindName("dgIntuneManagerObjects")
|
|
$script:spIntuneManagerButtons = $panel.FindName("spSubMenu")
|
|
$script:IntuneManagementFilterTextBox = $panel.FindName("txtFilter")
|
|
$script:IntuneManagementObjectsCount = $panel.FindName("txtObjectsCount")
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnCopy", "Add_Click", {
|
|
Copy-IntuneManagerPolicy
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnView", "Add_Click", {
|
|
Show-IntuneManagerDetailedView
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnCompare", "Add_Click", {
|
|
$selectedItems = @($script:dgIntuneManagerObjects.ItemsSource | Where-Object IsSelected -eq $true)
|
|
if($selectedItems.Count -eq 0)
|
|
{
|
|
$sel = $script:dgIntuneManagerObjects.SelectedItem
|
|
if($sel) { $selectedItems = @($sel) }
|
|
}
|
|
if($selectedItems.Count -gt 0) { Show-GraphCompareForm $selectedItems }
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnExport", "Add_Click", {
|
|
Show-IntuneManagerExportForm
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnImport", "Add_Click", {
|
|
Show-IntuneManagerImportForm
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnDelete", "Add_Click", {
|
|
Remove-GraphObjectsUI
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnDocument", "Add_Click", {
|
|
# Prefer IsSelected-checked rows; fall back to highlighted row; fall back
|
|
# to all currently-visible rows (matches the OLD project's btnDocument
|
|
# behaviour where clicking with nothing selected documented the view).
|
|
$items = @($script:dgIntuneManagerObjects.ItemsSource | Where-Object IsSelected -eq $true)
|
|
if ($items.Count -eq 0 -and $null -ne $script:dgIntuneManagerObjects.SelectedItem) {
|
|
$items = @($script:dgIntuneManagerObjects.SelectedItem)
|
|
}
|
|
if ($items.Count -eq 0) {
|
|
$items = @($script:dgIntuneManagerObjects.ItemsSource)
|
|
}
|
|
if ($items.Count -eq 0) { return }
|
|
Show-IntuneManagerDocumentForm -PolicyObject $items
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "txtFilter", "Add_TextChanged", {
|
|
Invoke-FilterBoxChanged $this $script:dgIntuneManagerObjects -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
})
|
|
|
|
$dpd = [System.ComponentModel.DependencyPropertyDescriptor]::FromProperty([System.Windows.Controls.ItemsControl]::ItemsSourceProperty, [System.Windows.Controls.DataGrid])
|
|
if($dpd)
|
|
{
|
|
$dpd.AddValueChanged($script:dgIntuneManagerObjects, {
|
|
$script:UIProvider.SetXamlProperty($this.Parent, "txtFilter", "Text", "")
|
|
# Clearing txtFilter only fires TextChanged when the text actually
|
|
# changes; for an already-empty filter that no-ops, leaving the
|
|
# objects-count label stale (or blank on first load). Refresh
|
|
# explicitly with -ForceUpdate so the count tracks every ItemsSource
|
|
# change — initial load, view switch, refresh, all paths.
|
|
Invoke-FilterBoxChanged $script:IntuneManagementFilterTextBox $script:dgIntuneManagerObjects -ForceUpdate -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
})
|
|
}
|
|
|
|
$script:dgIntuneManagerObjects.add_selectionChanged({
|
|
|
|
$hasSelectedItems = ($script:dgIntuneManagerObjects.ItemsSource | Measure-Object).Count -gt 0 -and (($script:dgIntuneManagerObjects.ItemsSource | Where-Object IsSelected -eq $true) -or ($null -ne $script:dgIntuneManagerObjects.SelectedItem))
|
|
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnView", "IsEnabled", $hasSelectedItems)
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnCompare", "IsEnabled", $hasSelectedItems)
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnCopy", "IsEnabled", $hasSelectedItems)
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnDelete", "IsEnabled", $hasSelectedItems)
|
|
$script:UIProvider.SetXamlProperty($this.Parent, "btnExport", "IsEnabled", $hasSelectedItems)
|
|
# btnDocument stays enabled whenever any items are loaded — clicking
|
|
# with nothing selected documents the entire view (matches OLD project).
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnDocument", "IsEnabled", (($script:dgIntuneManagerObjects.ItemsSource | Measure-Object).Count -gt 0))
|
|
|
|
Invoke-AppEvent "IntuneManagerUISelectedItemChanged" $hasSelectedItems
|
|
})
|
|
|
|
$btnRefresh = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\RefreshButton.xaml"))
|
|
if($btnRefresh)
|
|
{
|
|
$grdTitle = $panel.FindName("grdTitle")
|
|
$btnRefresh.SetValue([System.Windows.Controls.Grid]::ColumnProperty, $grdTitle.ColumnDefinitions.Count - 1)
|
|
$btnRefresh.Margin = "0,0,5,3"
|
|
$btnRefresh.Cursor = "Hand"
|
|
$btnRefresh.Name = "btnRefresh"
|
|
$btnRefresh.Focusable = $false
|
|
$grdTitle.Children.Add($btnRefresh) | Out-Null
|
|
|
|
$tooltip = [System.Windows.Controls.ToolTip]::new()
|
|
$tooltip.Content = "Refresh all objects"
|
|
[System.Windows.Controls.ToolTipService]::SetToolTip($btnRefresh, $tooltip)
|
|
|
|
$panel.RegisterName($btnRefresh.Name, $btnRefresh)
|
|
|
|
$tooltip = [System.Windows.Controls.ToolTip]::new()
|
|
$tooltip.Content = "Refresh objects"
|
|
|
|
[System.Windows.Controls.ToolTipService]::SetToolTip($btnRefresh, $tooltip)
|
|
|
|
$btnRefresh.Add_Click({
|
|
Invoke-RefreshObjects
|
|
})
|
|
}
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnLoadAllPages", "add_click", {
|
|
Add-GraphPoliciesFromPaging "AllRemainingPages"
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($panel, "btnLoadNextPage", "add_click", {
|
|
Add-GraphPoliciesFromPaging "NextPage"
|
|
})
|
|
|
|
return $panel
|
|
}
|
|
|
|
function Add-GraphPoliciesFromPaging
|
|
{
|
|
param(
|
|
[ValidateSet("NextPage", "AllRemainingPages")]
|
|
[string]
|
|
$PagingType
|
|
)
|
|
|
|
Write-Status "Loading $($script:IntuneManagerSelectedObject.Title) objects"
|
|
|
|
Get-GraphPolicies -Paging $PagingType | Get-GraphPolicyForUIList | ForEach-Object {
|
|
$script:intuneManagerPolicyCollection.Add([PSObject]$_)
|
|
}
|
|
$script:dgIntuneManagerObjects.ItemsSource.CommitNew()
|
|
|
|
Set-GraphPagesButtonStatus
|
|
Invoke-FilterBoxChanged $script:IntuneManagementFilterTextBox $script:dgIntuneManagerObjects -ForceUpdate -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
Write-Status ""
|
|
}
|
|
|
|
function Set-GraphPagesButtonStatus
|
|
{
|
|
$IntuneManagerViewObject = Get-SingletonObject "IntuneViewObject"
|
|
|
|
$IntuneManagerPanael = $IntuneManagerViewObject.ViewPanel
|
|
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "btnLoadAllPages", "Visibility", (?: ($script:GraphPagingCache) "Visible" "Collapsed"))
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "btnLoadNextPage", "Visibility", (?: ($script:GraphPagingCache) "Visible" "Collapsed"))
|
|
}
|
|
|
|
function Clear-GraphObjects
|
|
{
|
|
$IntuneManagerViewObject = Get-SingletonObject "IntuneViewObject"
|
|
|
|
$IntuneManagerPanael = $IntuneManagerViewObject.ViewPanel
|
|
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "txtFormTitle", "Text", "")
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "txtObjectsCount", "Text", "")
|
|
# Keep grdTitle visible — the empty colored strip is the desired idle state so
|
|
# the right pane isn't an unframed expanse of background.
|
|
|
|
if($script:dgIntuneManagerObjects -and $script:dgIntuneManagerObjects.Children) {
|
|
$script:dgIntuneManagerObjects.Children.Clear()
|
|
$script:dgIntuneManagerObjects.ItemsSource = $null
|
|
}
|
|
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
|
|
function Invoke-RefreshObjects
|
|
{
|
|
if(-not $script:IntuneManagerSelectedObject) { return }
|
|
$txtFilterText = $null
|
|
$txtFilter = $this.Parent.FindName("txtFilter")
|
|
if($txtFilter) { $txtFilterText = $txtFilter.Text }
|
|
|
|
Invoke-IntuneActivateObject $script:IntuneManagerSelectedObject
|
|
|
|
if($txtFilterText -and $txtFilter)
|
|
{
|
|
$txtFilter.Text = $txtFilterText
|
|
Invoke-FilterBoxChanged $txtFilter $script:dgIntuneManagerObjects -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
}
|
|
|
|
Write-Status ""
|
|
}
|
|
|
|
function Invoke-IntuneActivatingView
|
|
{
|
|
param($PreviousActiveView)
|
|
|
|
Add-GraphBulkMenu
|
|
|
|
if($null -eq $PreviousActiveView) {
|
|
Set-SplashWindowText "Authenticating"
|
|
$obj = Get-SingletonObject "IntuneViewObject"
|
|
if($obj) { $obj.Authenticate() }
|
|
}
|
|
}
|
|
|
|
function Invoke-IntuneDeactivatingView
|
|
{
|
|
param($NewActiveView)
|
|
|
|
$tmp = $script:mnuMain.Items | Where-Object Name -eq "IntuneBulk"
|
|
if($tmp) { $script:mnuMain.Items.Remove($tmp) }
|
|
}
|
|
|
|
function Add-GraphBulkMenu
|
|
{
|
|
# Item order matches the original project: Export, Import, Delete, Compare,
|
|
# Copy, Documentation. Features that are new in this version (Scope Tags,
|
|
# Assignments) are appended after a separator.
|
|
$menuItem = [System.Windows.Controls.MenuItem]::new()
|
|
$menuItem.Header = "_Bulk"
|
|
$menuItem.Name = "IntuneBulk"
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "_Export"
|
|
$subItem.Add_Click({Show-GraphBulkExportForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "_Import"
|
|
$subItem.Add_Click({Show-GraphBulkImportForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "_Delete"
|
|
$subItem.Name = "mnuBulkDelete"
|
|
$allowBulkDelete = Get-SettingValue "AllowBulkDelete"
|
|
# Add it hidden even if not enabled, the save settings will enable it
|
|
$subItem.Visibility = (?: ($allowBulkDelete -eq $true) "Visible" "Collapsed")
|
|
$subItem.Add_Click({Show-GraphBulkDeleteForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "C_ompare"
|
|
$subItem.Add_Click({ Show-GraphBulkCompareForm })
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "Cop_y"
|
|
$subItem.Add_Click({Show-GraphBulkCopyForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "Doc_umentation"
|
|
$subItem.Add_Click({Show-GraphBulkDocumentationForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$menuItem.AddChild(([System.Windows.Controls.Separator]::new())) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "_Scope Tags"
|
|
$subItem.Add_Click({Show-GraphBulkScopeTagForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
$subItem = [System.Windows.Controls.MenuItem]::new()
|
|
$subItem.Header = "_Assignments"
|
|
$subItem.Add_Click({Show-GraphBulkAssignmentsForm})
|
|
$menuItem.AddChild($subItem) | Out-Null
|
|
|
|
Add-MenuItem $menuItem 1
|
|
}
|
|
|
|
function Invoke-IntuneActivateObject
|
|
{
|
|
param($SelectedObject)
|
|
|
|
if($null -eq $SelectedObject) {
|
|
$script:dgIntuneManagerObjects.ItemsSource = $null
|
|
return
|
|
}
|
|
|
|
Write-Status "Activate $($SelectedObject.Title)"
|
|
|
|
$script:IntuneManagerSelectedObject = $SelectedObject
|
|
|
|
Clear-GraphObjects
|
|
|
|
Show-SelectedGraphPolicies -SinglePage -Restart
|
|
}
|
|
|
|
function Show-SelectedGraphPolicies
|
|
{
|
|
param(
|
|
[switch]
|
|
$AllPages,
|
|
[switch]
|
|
$SinglePage,
|
|
[switch]
|
|
$Restart
|
|
)
|
|
|
|
if($Restart -eq $true) {
|
|
$script:IntuneManagerCurrentPage = $null
|
|
}
|
|
|
|
if($null -eq $script:IntuneManagerSelectedObject) {
|
|
return
|
|
}
|
|
|
|
$params = @{}
|
|
if($script:IntuneManagerSelectedObject -is [IntunePolicyGroupBase]) {
|
|
$params.Add("PolicyGroup", $script:IntuneManagerSelectedObject.Id)
|
|
}
|
|
elseif($script:IntuneManagerSelectedObject -is [IntunePolicyTypeBase]) {
|
|
$params.Add("PolicyType", $script:IntuneManagerSelectedObject.Id)
|
|
}
|
|
|
|
if($SinglePage -eq $true -and (Get-SettingValue "GetAllPages") -ne $true) {
|
|
$params.Add("SinglePage", $true)
|
|
}
|
|
|
|
Write-Status "Loading $($script:IntuneManagerSelectedObject.Title) objects"
|
|
|
|
if($script:IntuneManagerSelectedObject.ShowForm -ne $false)
|
|
{
|
|
$IntuneManagerViewObject = Get-SingletonObject "IntuneViewObject"
|
|
$IntuneManagerPanael = $IntuneManagerViewObject.ViewPanel
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "txtFormTitle", "Text", $script:IntuneManagerSelectedObject.Title)
|
|
try {
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "ccIcon", "Content", $script:IntuneManagerSelectedObject.GetImage())
|
|
}
|
|
catch {
|
|
}
|
|
$script:UIProvider.SetXamlProperty($IntuneManagerPanael, "grdTitle", "Visibility", "Visible")
|
|
}
|
|
|
|
$graphObjects = Get-GraphPolicies @params | Get-GraphPolicyForUIList
|
|
|
|
$script:dgIntuneManagerObjects.AutoGenerateColumns = $false
|
|
$script:dgIntuneManagerObjects.Columns.Clear()
|
|
|
|
if($graphObjects)
|
|
{
|
|
$column = Get-GridCheckboxColumn "IsSelected"
|
|
$script:dgIntuneManagerObjects.Columns.Add($column)
|
|
|
|
$column.Header.add_Click({
|
|
foreach($Item in $script:dgIntuneManagerObjects.ItemsSource)
|
|
{
|
|
$Item.IsSelected = $this.IsChecked
|
|
}
|
|
$script:dgIntuneManagerObjects.Items.Refresh()
|
|
})
|
|
|
|
$additionalColumns = @()
|
|
$selectedType = Get-SelectedObjectTypeString
|
|
if(-not $selectedType) { return }
|
|
$additionalColsStr = Get-SettingStoreValue "IntuneManager\ObjectColumns\$selectedType" "$($script:IntuneManagerSelectedObject.Id)"
|
|
|
|
if($additionalColsStr)
|
|
{
|
|
$additionalColumns += $additionalColsStr.Split(',')
|
|
}
|
|
|
|
$columns = @()
|
|
|
|
if($additionalColumns.Count -eq 0 -or $additionalColumns[0] -ne "0")
|
|
{
|
|
$columns += ?? $script:IntuneManagerSelectedObject.ViewProperties @("Name","PolicyName","ID")
|
|
}
|
|
|
|
# Add custom columns
|
|
foreach($additionalCol in $additionalColumns)
|
|
{
|
|
if($additionalCol -eq "0" -or $additionalCol -eq "1") { continue }
|
|
$columns += $additionalCol
|
|
}
|
|
|
|
# Add columns
|
|
# One line per row: a multi-line value (store app descriptions, script
|
|
# bodies) otherwise makes the row as tall as its text and wrecks scrolling.
|
|
# The full text stays on the cell tooltip. Setting: ObjectListFirstLineOnly.
|
|
$firstLineOnly = ((Get-SettingValue "ObjectListFirstLineOnly") -eq $true)
|
|
# Character cap on top of the first line: one very long description
|
|
# otherwise widens its column until the others leave the screen.
|
|
$maxCellLength = 50
|
|
try { $maxCellLength = [int](Get-SettingValue "ObjectListMaxCellLength") } catch { }
|
|
foreach($columnInfo in $columns)
|
|
{
|
|
$bindingProp,$colHeader = $columnInfo.Split('=')
|
|
if(-not $colHeader) { $colHeader = $bindingProp.Split(".")[-1] }
|
|
|
|
$dgIntuneManagerObjects.Columns.Add((New-GridTextColumn -Path $bindingProp -Header $colHeader -FirstLineOnly:$firstLineOnly -MaxLength $maxCellLength))
|
|
}
|
|
}
|
|
else {
|
|
$graphObjects = @()
|
|
}
|
|
|
|
$script:intuneManagerPolicyCollection = [System.Collections.ObjectModel.ObservableCollection[PSObject]]::new($graphObjects)
|
|
$script:dgIntuneManagerObjects.ItemsSource = [System.Windows.Data.CollectionViewSource]::GetDefaultView($script:intuneManagerPolicyCollection)
|
|
|
|
$policyTypes = Get-IntuneManagerSelectedPolicyTypes
|
|
|
|
$allowDelete = Get-SettingValue "AllowDelete"
|
|
|
|
Set-IntuneManagerUIButtonStatus @("btnDelete") $policyTypes -ForceHide:($allowDelete -eq $false)
|
|
|
|
Set-IntuneManagerUIButtonStatus @("btnImport","btnView","btnExport","btnCompare","btnCopy","btnDocument") $policyTypes
|
|
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, "btnImport", "IsEnabled", $true) # Always allow Import if ObjectType allows it
|
|
|
|
Set-GraphPagesButtonStatus
|
|
|
|
Invoke-AppEvent "IntuneManagerUISelectedMenuItemChanged" $policyTypes
|
|
|
|
Write-Status ""
|
|
}
|
|
|
|
function Set-IntuneManagerUIButtonStatus
|
|
{
|
|
param($Buttons, $PolicyTypes, [switch]$ForceHide)
|
|
|
|
foreach($btn in $Buttons) {
|
|
$visibility = "Collapsed"
|
|
if($ForceHide -ne $true) {
|
|
foreach($policyType in $PolicyTypes) {
|
|
if(-not $policyType.ShowButtons -or ($policyType.ShowButtons | Where-Object { $btn -like "*$($_)" } )) {
|
|
$visibility = "Visible"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
$script:UIProvider.SetXamlProperty($script:spIntuneManagerButtons, $btn, "Visibility", $visibility)
|
|
}
|
|
}
|
|
|
|
function Get-GraphPolicyForUIList
|
|
{
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
|
|
[IntunePolicyBase[]]
|
|
$InputObject
|
|
)
|
|
|
|
Begin { $graphObjects = @() }
|
|
|
|
Process {
|
|
foreach($policy in $InputObject) {
|
|
$tmp = [PSCustomObject]$policy
|
|
if((-not $tmp.PSObject.Properties['JsonObject']) -or ($null -eq $tmp.JsonObject)) {
|
|
$tmp | Add-Member -NotePropertyName "JsonObject" -NotePropertyValue $policy.Object -Force
|
|
}
|
|
if((-not $tmp.PSObject.Properties['_TokenId']) -or ($null -eq $tmp._TokenId)) {
|
|
$tmp | Add-Member -NotePropertyName "_TokenId" -NotePropertyValue $policy.TokenId -Force
|
|
}
|
|
$tmp | Add-Member -NotePropertyName "IsSelected" -NotePropertyValue $false
|
|
$graphObjects += $tmp
|
|
}
|
|
}
|
|
|
|
End { $graphObjects | Sort-Object -Property Name }
|
|
}
|
|
|
|
function Get-SelectedObjectTypeString {
|
|
|
|
if($script:IntuneManagerSelectedObject -is [IntunePolicyGroupBase]) {
|
|
"PolicyGroup"
|
|
}
|
|
elseif($script:IntuneManagerSelectedObject -is [IntunePolicyTypeBase]) {
|
|
"PolicyType"
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
}
|
|
|
|
|
|
function Get-IntuneManagerSelectedPolicyTypes
|
|
{
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
$policyTypes = @()
|
|
|
|
if($script:IntuneManagerSelectedObject -is [IntunePolicyGroupBase]) {
|
|
$policyTypes += $script:IntuneManagerSelectedObject.PolicyTypes
|
|
}
|
|
else {
|
|
$policyTypes += $script:IntuneManagerSelectedObject
|
|
}
|
|
return $policyTypes
|
|
}
|
|
|
|
function Invoke-IntuneUIAppInitialized
|
|
{
|
|
# Settings registrations moved out of this handler (2026-08-15):
|
|
# GraphPageSize -> Internal/IntuneManager.ps1 (engine-consumed);
|
|
# GetAllPages / ObjectViewType -> UI/Classes/UICommonSettings.ps1 (shared by
|
|
# both backends, registered once).
|
|
|
|
Add-ViewObject (Get-SingletonObject "IntuneViewObject")
|
|
|
|
Add-AppEventHandler "AuthenticatedNewToken" "Invoke-IntuneUIEventNewAuthentication"
|
|
Add-AppEventHandler "AuthenticationUserDisconnected" "Invoke-IntuneUIEventUserDisconnected"
|
|
Add-AppEventHandler "AuthenticationFailed" "Invoke-IntuneUIEventAuthenticationFailed"
|
|
}
|
|
|
|
function Invoke-IntuneUIEventSettingsUpdated
|
|
{
|
|
if((Get-SettingValue "ObjectViewType") -ne (Get-CacheObject "ObjectViewType-Cached")) {
|
|
Set-CacheObject "ObjectViewType-Cached" (Get-SettingValue "ObjectViewType")
|
|
Show-ViewMenu
|
|
}
|
|
|
|
Update-IntuneDeleteVisibility
|
|
}
|
|
|
|
# Called by Show-ViewMenu (CoreUI) whenever the IntuneView becomes the
|
|
# active view OR its menu is rebuilt. Makes the title-bar config gear
|
|
# visible, syncs all its checkable states to current settings, and
|
|
# one-shot wires the click handlers. If "show item counts" is on, also
|
|
# kicks the count fetcher to paint counts into the freshly-bound rows.
|
|
function Update-MenuTitleConfigForIntuneView
|
|
{
|
|
if(-not $script:btnMenuTitleConfig) { return }
|
|
|
|
$script:btnMenuTitleConfig.Visibility = "Visible"
|
|
$script:btnMenuTitleConfig.ToolTip = "Menu options: switch view, refresh"
|
|
|
|
$current = Get-SettingValue "ObjectViewType"
|
|
# "Group" is the default (matches the ObjectViewType DefaultValue registered in
|
|
# UI/Classes/UICommonSettings.ps1);
|
|
# anything not "Type" reads as Group so a missing/blank setting still
|
|
# ticks the Group option rather than leaving both unchecked.
|
|
if($script:mnuMenuTitleConfigGroup) { $script:mnuMenuTitleConfigGroup.IsChecked = ($current -ne "Type") }
|
|
if($script:mnuMenuTitleConfigType) { $script:mnuMenuTitleConfigType.IsChecked = ($current -eq "Type") }
|
|
|
|
# Read-only "Intune Info" group is opt-in (hidden by default). Sync the toggle.
|
|
if($script:mnuMenuTitleConfigShowInfo) { $script:mnuMenuTitleConfigShowInfo.IsChecked = ((Get-SettingStoreValue "IntuneManager" "ShowIntuneInfo" "false") -eq "true") }
|
|
|
|
# "Show item counts" is HIDDEN per user decision (2026-06-12) — the menu
|
|
# item is collapsed in MainWindow.xaml and the populate paths below are
|
|
# forced off so a previously-saved true setting can't resurrect it. The
|
|
# counts machinery (Get-IntuneTypeCounts + label projection) is kept for
|
|
# potential re-enable.
|
|
$showCounts = $false
|
|
if($script:mnuMenuTitleConfigShowCounts) { $script:mnuMenuTitleConfigShowCounts.IsChecked = $showCounts }
|
|
|
|
# Wire-once. The Click handlers persist for the window lifetime so a
|
|
# subsequent view switch + return doesn't re-register them.
|
|
if(-not $script:_menuTitleConfigWired) {
|
|
$script:_menuTitleConfigWired = $true
|
|
|
|
$script:mnuMenuTitleConfigGroup.Add_Click({ Set-IntuneMenuObjectViewType "Group" })
|
|
$script:mnuMenuTitleConfigType.Add_Click({ Set-IntuneMenuObjectViewType "Type" })
|
|
|
|
if($script:mnuMenuTitleConfigShowInfo) {
|
|
$script:mnuMenuTitleConfigShowInfo.Add_Click({
|
|
# IsCheckable items flip IsChecked before the handler runs. Persist +
|
|
# rebuild the nav so the read-only Info group appears/disappears.
|
|
$enabled = [bool]$script:mnuMenuTitleConfigShowInfo.IsChecked
|
|
Save-SettingStoreValue -SubPath "IntuneManager" -Key "ShowIntuneInfo" -Value ($enabled.ToString().ToLower())
|
|
Show-ViewMenu
|
|
})
|
|
}
|
|
|
|
if($script:mnuMenuTitleConfigRefresh) {
|
|
$script:mnuMenuTitleConfigRefresh.Add_Click({
|
|
# Re-bind the menu list (cheap — no Graph). Count re-fetch
|
|
# removed with the hidden "Show item counts" feature.
|
|
Show-ViewMenu
|
|
})
|
|
}
|
|
|
|
if($script:mnuMenuTitleConfigShowCounts) {
|
|
$script:mnuMenuTitleConfigShowCounts.Add_Click({
|
|
# IsCheckable items flip IsChecked before the handler runs.
|
|
$enabled = [bool]$script:mnuMenuTitleConfigShowCounts.IsChecked
|
|
Save-SettingStoreValue -SubPath "IntuneManager" -Key "ShowMenuItemCounts" -Value ($enabled.ToString().ToLower())
|
|
if($enabled) {
|
|
Update-IntuneViewItemCounts
|
|
}
|
|
else {
|
|
# Strip counts back to bare Title without a fresh batch.
|
|
if($script:lstMenuItems -and $script:lstMenuItems.ItemsSource) {
|
|
foreach($it in $script:lstMenuItems.ItemsSource) {
|
|
$it | Add-Member -NotePropertyName "MenuLabel" -NotePropertyValue $it.Title -Force
|
|
}
|
|
try { $script:lstMenuItems.Items.Refresh() } catch { }
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
if($script:mnuMenuTitleConfigFilterPlatforms -and $script:btnMenuTitleConfig.ContextMenu) {
|
|
# Populate on the parent ContextMenu's Opened (not the submenu's
|
|
# SubmenuOpened). A MenuItem with zero child items doesn't render
|
|
# the submenu arrow and never fires SubmenuOpened — so the items
|
|
# have to exist BEFORE the user hovers into the submenu. Cheap:
|
|
# in-memory scan of the already-bound right-pane rows, no Graph.
|
|
$script:btnMenuTitleConfig.ContextMenu.Add_Opened({ Update-IntuneFilterPlatformsSubmenu })
|
|
}
|
|
}
|
|
|
|
# First paint after rebind: if counts are on, populate them. Uses the
|
|
# cached $script:_intuneTypeCounts so a view-switch back doesn't trigger
|
|
# a fresh batch — only first activation or explicit Refresh does.
|
|
if($showCounts) {
|
|
Update-IntuneViewItemCounts
|
|
}
|
|
}
|
|
|
|
# Rebuilds the Filter Platforms submenu from the currently-bound right-pane
|
|
# rows. Each entry is an IsCheckable MenuItem keyed on the platform string
|
|
# (with "" sentinel for null/empty Platform). Currently-selected platforms
|
|
# are always present even if absent from the data, so the user can untick
|
|
# a filter they previously enabled but that no longer matches anything.
|
|
function Update-IntuneFilterPlatformsSubmenu
|
|
{
|
|
if(-not $script:mnuMenuTitleConfigFilterPlatforms) { return }
|
|
|
|
# 1. Collect platforms in the current grid
|
|
$available = [System.Collections.Generic.HashSet[string]]::new()
|
|
$hasNull = $false
|
|
if($script:dgIntuneManagerObjects -and $script:dgIntuneManagerObjects.ItemsSource) {
|
|
$src = $script:dgIntuneManagerObjects.ItemsSource
|
|
if($src -is [System.Windows.Data.ListCollectionView]) { $src = $src.SourceCollection }
|
|
if($src) {
|
|
foreach($it in $src) {
|
|
$plat = $it.Platform
|
|
if([string]::IsNullOrEmpty($plat)) { $hasNull = $true }
|
|
else { [void]$available.Add([string]$plat) }
|
|
}
|
|
}
|
|
}
|
|
|
|
# 2. Include currently-selected filters so the user can untick them even
|
|
# if the new view no longer has any matching rows.
|
|
if($script:_intunePlatformFilter) {
|
|
foreach($s in $script:_intunePlatformFilter) {
|
|
if($s -eq "") { $hasNull = $true }
|
|
else { [void]$available.Add($s) }
|
|
}
|
|
}
|
|
|
|
$script:mnuMenuTitleConfigFilterPlatforms.Items.Clear()
|
|
|
|
$sorted = @($available | Sort-Object)
|
|
if($sorted.Count -eq 0 -and -not $hasNull) {
|
|
$empty = [System.Windows.Controls.MenuItem]::new()
|
|
$empty.Header = "(no policies loaded)"
|
|
$empty.IsEnabled = $false
|
|
[void]$script:mnuMenuTitleConfigFilterPlatforms.Items.Add($empty)
|
|
return
|
|
}
|
|
|
|
# 3. One MenuItem per platform; "(no platform)" pinned at the bottom so
|
|
# it visually separates from named platforms.
|
|
foreach($plat in $sorted) {
|
|
$mi = [System.Windows.Controls.MenuItem]::new()
|
|
$mi.Header = $plat
|
|
$mi.IsCheckable = $true
|
|
$mi.IsChecked = ($script:_intunePlatformFilter -and $script:_intunePlatformFilter.Contains($plat))
|
|
# Tag stores the canonical key (== Header for named platforms; "" for
|
|
# the no-platform row). Read in the click handler so the same handler
|
|
# works for both.
|
|
$mi.Tag = $plat
|
|
$mi.Add_Click({ Switch-IntunePlatformFilter ([string]$this.Tag) ([bool]$this.IsChecked) })
|
|
[void]$script:mnuMenuTitleConfigFilterPlatforms.Items.Add($mi)
|
|
}
|
|
if($hasNull) {
|
|
$mi = [System.Windows.Controls.MenuItem]::new()
|
|
$mi.Header = "(no platform)"
|
|
$mi.IsCheckable = $true
|
|
$mi.IsChecked = ($script:_intunePlatformFilter -and $script:_intunePlatformFilter.Contains(""))
|
|
$mi.Tag = ""
|
|
$mi.Add_Click({ Switch-IntunePlatformFilter ([string]$this.Tag) ([bool]$this.IsChecked) })
|
|
[void]$script:mnuMenuTitleConfigFilterPlatforms.Items.Add($mi)
|
|
}
|
|
|
|
# 4. "Clear filter" footer when any filter is active.
|
|
if($script:_intunePlatformFilter -and $script:_intunePlatformFilter.Count -gt 0) {
|
|
[void]$script:mnuMenuTitleConfigFilterPlatforms.Items.Add([System.Windows.Controls.Separator]::new())
|
|
$clear = [System.Windows.Controls.MenuItem]::new()
|
|
$clear.Header = "Clear filter"
|
|
$clear.Add_Click({
|
|
if($script:_intunePlatformFilter) { $script:_intunePlatformFilter.Clear() }
|
|
if($script:dgIntuneManagerObjects) {
|
|
Invoke-FilterBoxChanged $script:IntuneManagementFilterTextBox $script:dgIntuneManagerObjects -ForceUpdate -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
}
|
|
})
|
|
[void]$script:mnuMenuTitleConfigFilterPlatforms.Items.Add($clear)
|
|
}
|
|
}
|
|
|
|
# Toggle a single platform on/off in $script:_intunePlatformFilter and
|
|
# re-evaluate the grid filter. Centralised so the submenu click handlers
|
|
# stay one-liners.
|
|
function Switch-IntunePlatformFilter
|
|
{
|
|
param([string]$PlatformKey, [bool]$Enable)
|
|
|
|
if($null -eq $script:_intunePlatformFilter) {
|
|
$script:_intunePlatformFilter = [System.Collections.Generic.HashSet[string]]::new()
|
|
}
|
|
if($Enable) { [void]$script:_intunePlatformFilter.Add($PlatformKey) }
|
|
else { [void]$script:_intunePlatformFilter.Remove($PlatformKey) }
|
|
|
|
if($script:dgIntuneManagerObjects) {
|
|
Invoke-FilterBoxChanged $script:IntuneManagementFilterTextBox $script:dgIntuneManagerObjects -ForceUpdate -ObjectsCountTextBox $script:IntuneManagementObjectsCount
|
|
}
|
|
}
|
|
|
|
# Single source of truth for switching the menu mode from the gear. Persists
|
|
# the setting so the choice survives a restart, keeps the SettingsUpdated
|
|
# cache key in sync so the Settings dialog won't fire a redundant rebuild,
|
|
# and rebuilds the menu list. Idempotent — selecting the same option twice
|
|
# is a no-op.
|
|
function Set-IntuneMenuObjectViewType
|
|
{
|
|
param([ValidateSet("Group","Type")][string]$ViewType)
|
|
|
|
$current = Get-SettingValue "ObjectViewType"
|
|
if($current -eq $ViewType) {
|
|
# Sync checkboxes anyway in case the user toggled one off — IsCheckable
|
|
# menu items uncheck on click before this handler runs.
|
|
if($script:mnuMenuTitleConfigGroup) { $script:mnuMenuTitleConfigGroup.IsChecked = ($ViewType -eq "Group") }
|
|
if($script:mnuMenuTitleConfigType) { $script:mnuMenuTitleConfigType.IsChecked = ($ViewType -eq "Type") }
|
|
return
|
|
}
|
|
|
|
Save-SettingStoreValue -SubPath "IntuneManager" -Key "ObjectViewType" -Value $ViewType
|
|
Set-CacheObject "ObjectViewType-Cached" $ViewType
|
|
|
|
if($script:mnuMenuTitleConfigGroup) { $script:mnuMenuTitleConfigGroup.IsChecked = ($ViewType -eq "Group") }
|
|
if($script:mnuMenuTitleConfigType) { $script:mnuMenuTitleConfigType.IsChecked = ($ViewType -eq "Type") }
|
|
|
|
Show-ViewMenu
|
|
}
|
|
|
|
function Invoke-IntuneUIEventShowMainWindow
|
|
{
|
|
Update-IntuneDeleteVisibility
|
|
}
|
|
|
|
function Invoke-IntuneUISelectedItemChanged
|
|
{
|
|
|
|
}
|
|
|
|
function Invoke-IntuneUIEventNewAuthentication
|
|
{
|
|
param($TokenInfo)
|
|
|
|
Update-IntuneDeleteVisibility
|
|
|
|
# Access marking (Internal/AccessLevel.ps1) needs the signed-in token, which
|
|
# only exists once login completes. The nav was built white before the token
|
|
# arrived, so rebuild it now to repaint the AccessType colours - otherwise the
|
|
# menu stays uncoloured until the next view switch. AccessType is a plain
|
|
# property with no change notification, so only a full ItemsSource rebuild
|
|
# re-runs the colour triggers; refreshing the object grid alone does nothing
|
|
# for the menu. Preserve and restore the selected menu item across the
|
|
# rebuild: setting ItemsSource nulls the selection, which fires
|
|
# OnItemChanged($null) and would blank a loaded object list. Restoring it
|
|
# reloads that list (the same effect the old Invoke-RefreshObjects had).
|
|
if(($script:ActiveView -and $script:ActiveView.ID -eq "IntuneManagement") -and
|
|
(Get-Command Show-ViewMenu -ErrorAction SilentlyContinue)) {
|
|
$selected = if($script:lstMenuItems) { $script:lstMenuItems.SelectedItem } else { $null }
|
|
Show-ViewMenu
|
|
if($selected -and $script:lstMenuItems) { $script:lstMenuItems.SelectedItem = $selected }
|
|
}
|
|
else {
|
|
Invoke-RefreshObjects
|
|
}
|
|
}
|
|
|
|
function Invoke-IntuneUIEventUserDisconnected
|
|
{
|
|
param($TokenInfo)
|
|
|
|
Update-IntuneDeleteVisibility
|
|
|
|
# Sign-out is the mirror of Invoke-IntuneUIEventNewAuthentication: with no
|
|
# token, Update-IntuneAccessLevels (run inside Show-ViewMenu -> GetViewItems)
|
|
# resets every type/group back to Full, clearing the orange/red marking. But
|
|
# AccessType is a plain property with no change notification, so the colours
|
|
# only clear when the menu's ItemsSource is rebuilt - rebuild it here.
|
|
# Unlike the login handler, do NOT restore the selection: letting it fall to
|
|
# null fires OnItemChanged($null), which clears the object list that the
|
|
# signed-out session can no longer load.
|
|
if(($script:ActiveView -and $script:ActiveView.ID -eq "IntuneManagement") -and
|
|
(Get-Command Show-ViewMenu -ErrorAction SilentlyContinue)) {
|
|
Show-ViewMenu
|
|
}
|
|
}
|
|
|
|
function Invoke-IntuneUIEventAuthenticationFailed
|
|
{
|
|
param($TokenInfo)
|
|
|
|
# AuthenticationFailed also fires on transient failures - this tenant's WAM
|
|
# broker fails every silent refresh while the token is still perfectly valid
|
|
# (see the SuppressFailedEvent handling in the auth core) - so rebuilding on
|
|
# every failure would wrongly wipe the marking mid-session. Only treat it as a
|
|
# sign-out when the default token is genuinely past expiry; Test-DefaultTokenExpired
|
|
# returns $false for still-valid and SDK-managed tokens. When it is expired, the
|
|
# reset is identical to an explicit disconnect, so reuse that handler (no
|
|
# selection restore -> the object grid clears too).
|
|
if(-not (Test-DefaultTokenExpired)) { return }
|
|
Invoke-IntuneUIEventUserDisconnected $TokenInfo
|
|
}
|
|
|
|
# Refresh the visibility of every place that exposes a delete action so that toggling
|
|
# 'AllowDelete' / 'AllowBulkDelete' in Settings or signing in to a new tenant takes effect
|
|
# without having to restart the UI or re-enter a view. Safe to call before the relevant
|
|
# controls exist -- each path checks for the control first.
|
|
function Update-IntuneDeleteVisibility
|
|
{
|
|
Update-IntuneManagerDeleteButton
|
|
Update-IntuneBulkDeleteMenu
|
|
}
|
|
|
|
function Update-IntuneManagerDeleteButton
|
|
{
|
|
if(-not $script:spIntuneManagerButtons) { return }
|
|
$btn = $script:spIntuneManagerButtons.FindName("btnDelete")
|
|
if(-not $btn) { return }
|
|
|
|
$allowDelete = (Get-SettingValue "AllowDelete") -eq $true
|
|
# Reuse the regular button-status path so the current policy type's ShowButtons rules
|
|
# still apply -- don't expose Delete when no compatible type is selected.
|
|
$policyTypes = @()
|
|
if(Get-Command Get-IntuneManagerSelectedPolicyTypes -ErrorAction SilentlyContinue)
|
|
{
|
|
$policyTypes = @(Get-IntuneManagerSelectedPolicyTypes)
|
|
}
|
|
Set-IntuneManagerUIButtonStatus @("btnDelete") $policyTypes -ForceHide:(-not $allowDelete)
|
|
}
|
|
|
|
function Update-IntuneBulkDeleteMenu
|
|
{
|
|
if(-not $script:mnuMain) { return }
|
|
$bulkMenu = $script:mnuMain.Items | Where-Object { $_.Name -eq "IntuneBulk" } | Select-Object -First 1
|
|
if(-not $bulkMenu) { return }
|
|
$deleteItem = $bulkMenu.Items | Where-Object { $_.Name -eq "mnuBulkDelete" } | Select-Object -First 1
|
|
if(-not $deleteItem) { return }
|
|
|
|
$allowBulkDelete = (Get-SettingValue "AllowBulkDelete") -eq $true
|
|
$deleteItem.Visibility = if($allowBulkDelete) { "Visible" } else { "Collapsed" }
|
|
}
|
|
|
|
#endregion
|
|
|
|
function Invoke-FilterBoxChanged
|
|
{
|
|
param($TxtBox, $DataSource, [switch]$ForceUpdate, $ObjectsCountTextBox)
|
|
|
|
if($DataSource.ItemsSource -is [System.Windows.Data.ListCollectionView])
|
|
{
|
|
# Capture the active text + platform filter into the predicate closure.
|
|
# The platform set lives at $script: so a separate code path (the gear's
|
|
# Filter Platforms submenu) can mutate it and then call this with
|
|
# -ForceUpdate to re-evaluate the bound rows.
|
|
$textValue = if($TxtBox) { [string]$TxtBox.Text } else { "" }
|
|
$platformSet = $script:_intunePlatformFilter
|
|
$hasText = -not [string]::IsNullOrEmpty($textValue)
|
|
$hasPlatform = ($null -ne $platformSet -and $platformSet.Count -gt 0)
|
|
|
|
if($hasText -or $hasPlatform) {
|
|
# Text match runs ONLY against the fields actually shown in this grid,
|
|
# derived from the grid's own bound columns (Name, Description, and
|
|
# whatever else the view/ObjectColumns configure). Deriving from
|
|
# $DataSource.Columns keeps this generic for every grid that calls this
|
|
# function AND avoids matching heavy off-grid note-properties - the row
|
|
# objects carry the full policy under 'JsonObject' plus '_TokenId', and
|
|
# regex-matching those on every row/keystroke was the source of the lag.
|
|
# Computed once here (not per row) and captured in the closure.
|
|
$columnPaths = @()
|
|
foreach($col in $DataSource.Columns) {
|
|
if($col -is [System.Windows.Controls.DataGridBoundColumn] -and $col.Binding -and $col.Binding.Path) {
|
|
$p = [string]$col.Binding.Path.Path
|
|
if($p -and $p -ne "IsSelected") { $columnPaths += $p }
|
|
}
|
|
}
|
|
# -like with the wildcard metacharacters escaped == a plain, fast,
|
|
# case-insensitive substring match (no regex engine per field).
|
|
$likePattern = if($hasText) { "*" + [System.Management.Automation.WildcardPattern]::Escape($textValue) + "*" } else { $null }
|
|
|
|
$DataSource.ItemsSource.Filter = {
|
|
param($Item)
|
|
|
|
# Platform filter — empty Platform maps to the "" sentinel so a
|
|
# "(no platform)" selection can target rows whose .Platform is
|
|
# null/empty without ambiguity.
|
|
if($hasPlatform) {
|
|
$plat = $Item.Platform
|
|
$key = if([string]::IsNullOrEmpty($plat)) { "" } else { [string]$plat }
|
|
if(-not $platformSet.Contains($key)) { return $false }
|
|
}
|
|
|
|
if($hasText) {
|
|
if($columnPaths.Count -gt 0) {
|
|
foreach($path in $columnPaths) {
|
|
# Resolve the (possibly dotted, e.g. "PolicyType.Title") bound path.
|
|
$val = $Item
|
|
foreach($seg in $path.Split('.')) {
|
|
if($null -eq $val) { break }
|
|
$val = $val.$seg
|
|
}
|
|
if($null -ne $val -and ([string]$val) -like $likePattern) { return $true }
|
|
}
|
|
return $false
|
|
}
|
|
# Defensive fallback (grid exposed no bound columns): scan
|
|
# properties but skip the internal/heavy ones so it can never
|
|
# regress to matching the whole policy object.
|
|
return ($null -ne ($Item.PSObject.Properties | Where-Object {
|
|
$_.Name -notin @("IsSelected","Object","ObjectType","JsonObject","_TokenId") -and
|
|
([string]$_.Value) -like $likePattern
|
|
}))
|
|
}
|
|
return $true
|
|
}.GetNewClosure()
|
|
}
|
|
else {
|
|
$DataSource.ItemsSource.Filter = $null
|
|
}
|
|
|
|
if($ForceUpdate)
|
|
{
|
|
$DataSource.ItemsSource.Refresh()
|
|
}
|
|
}
|
|
|
|
if($ObjectsCountTextBox)
|
|
{
|
|
# loadedCount = rows pulled from Graph and cached locally (across all loaded pages)
|
|
# visibleCount = rows currently rendered after the txtFilter predicate
|
|
# If $script:GraphPagingCache is non-empty, more pages are available on the server
|
|
# (the Load More / Load All buttons are visible). We mark loadedCount with a "+"
|
|
# in that case so the user can tell the count isn't the full set.
|
|
$loadedCount = 0
|
|
if($DataSource.ItemsSource.SourceCollection) {
|
|
$loadedCount = $DataSource.ItemsSource.SourceCollection.Count
|
|
}
|
|
$visibleCount = ($DataSource.ItemsSource | Measure-Object).Count
|
|
$morePages = [bool]$script:GraphPagingCache
|
|
$loadedLabel = if($morePages) { "$loadedCount+" } else { "$loadedCount" }
|
|
|
|
if($loadedCount -le 0) {
|
|
$ObjectsCountTextBox.Text = ""
|
|
}
|
|
elseif($visibleCount -lt $loadedCount) {
|
|
# Filter is active and hides some loaded rows.
|
|
$ObjectsCountTextBox.Text = "Showing $visibleCount of $loadedLabel"
|
|
}
|
|
else {
|
|
# No filter (or filter matches everything loaded).
|
|
$ObjectsCountTextBox.Text = if($morePages) {
|
|
"Objects: $loadedLabel (more available - click Load All)"
|
|
} else {
|
|
"Objects: $loadedCount"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Bulk Forms
|