diff --git a/Core.psm1 b/Core.psm1
index 2f73e2a..47cb84d 100644
--- a/Core.psm1
+++ b/Core.psm1
@@ -12,7 +12,7 @@ This module handles the WPF UI
function Get-ModuleVersion
{
- '3.0.0'
+ '3.0.1'
}
function Start-CoreApp
@@ -1138,8 +1138,8 @@ function Add-ViewItem
{
param($viewItem)
- $objSection = $global:viewObjects | Where { $_.ViewInfo.Id -eq $viewItem.ViewID }
- if(-not $objSection)
+ $viewObject = $global:viewObjects | Where { $_.ViewInfo.Id -eq $viewItem.ViewID }
+ if(-not $viewObject)
{
if(($arrMenuInlcude -and $arrMenuInlcude -notcontains $viewItem.ViewID) -or ($arrMenuExlcude -and $arrMenuExlcude -contains $viewItem.ViewID)) { return }
@@ -1153,17 +1153,9 @@ function Add-ViewItem
$viewItem | Add-Member -NotePropertyName "ImportOrder" -NotePropertyValue 1000
}
- if(-not $global:PermissionScope) { $global:PermissionScope = @() }
foreach($scope in $viewItem.Permissons)
{
- if($global:PermissionScope -notcontains $scope) { $global:PermissionScope += $scope }
- }
-
- foreach($required in @("openid","profile","email","User.ReadWrite.All","Group.ReadWrite.All","RoleManagement.Read.Directory")) #,"https://management.azure.com/user_impersonation") )
- {
- if($required -in $global:PermissionScope) { continue }
- $global:PermissionScope += $required
- Write-LogDebug "Adding required scope $required"
+ if($viewObject.ViewInfo.Permissions -is [Object[]] -and $viewObject.ViewInfo.Permissions -notcontains $scope) { $viewObject.ViewInfo.Permissions += $scope }
}
if($viewItem.Icon -or [IO.File]::Exists(($global:AppRootFolder + "\Xaml\Icons\$($viewItem.Id).xaml")))
@@ -1172,7 +1164,7 @@ function Add-ViewItem
$viewItem | Add-Member -NotePropertyName "IconImage" -NotePropertyValue $ctrl
}
- $objSection.ViewItems += $viewItem
+ $viewObject.ViewItems += $viewItem
}
function Show-View
@@ -1221,7 +1213,7 @@ function Show-View
{
$global:txtSplashText.Text = "Authenticate"
[System.Windows.Forms.Application]::DoEvents()
- & $viewObject.ViewInfo.Authenticate
+ & $viewObject.ViewInfo.Authenticate
}
if($viewObject.ViewInfo.Activating)
diff --git a/Extensions/Compare.psm1 b/Extensions/Compare.psm1
index 5259171..1154299 100644
--- a/Extensions/Compare.psm1
+++ b/Extensions/Compare.psm1
@@ -11,7 +11,7 @@ Objects can be compared based on Properties or Documentatation info.
function Get-ModuleVersion
{
- '1.0.0'
+ '1.0.1'
}
function Invoke-InitializeModule
diff --git a/Extensions/Documentation.psm1 b/Extensions/Documentation.psm1
index 9ef21b2..518ad5c 100644
--- a/Extensions/Documentation.psm1
+++ b/Extensions/Documentation.psm1
@@ -20,7 +20,7 @@ $global:documentationProviders = @()
function Get-ModuleVersion
{
- '1.0.0'
+ '1.0.1'
}
function Invoke-InitializeModule
@@ -411,6 +411,10 @@ function Get-ObjectTypeString
{
return (Get-LanguageString "SecurityTemplate.conditionalAccess")
}
+ elseif($objTypeId -eq "EndpointAnalytics")
+ {
+ return (Get-LanguageString "SettingDetails.healthMonScopeBootPerf")
+ }
elseif($objTypeId -eq "EndpointSecurity")
{
return (Get-LanguageString "PolicyType.EndpointSecurityTemplate.default")
diff --git a/Extensions/EndpointManager.psm1 b/Extensions/EndpointManager.psm1
index 7ee804d..fa72890 100644
--- a/Extensions/EndpointManager.psm1
+++ b/Extensions/EndpointManager.psm1
@@ -10,7 +10,7 @@ This module is for the Endpoint Manager/Intune View. It manages Export/Import/Co
#>
function Get-ModuleVersion
{
- '3.1.0'
+ '3.1.2'
}
function Invoke-InitializeModule
@@ -20,7 +20,7 @@ function Invoke-InitializeModule
Title = "Endpoint Manager/Intune"
Id = "EndpointManager"
Values = @()
- Priority = 10
+ Priority = 10
})
Add-SettingsObject (New-Object PSObject -Property @{
@@ -72,6 +72,22 @@ function Invoke-InitializeModule
SubPath = "EndpointManager"
}) "EndpointManager"
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Show Delete button"
+ Key = "EMAllowDelete"
+ Type = "Boolean"
+ DefaultValue = $false
+ Description = "Allow deleting individual objectes"
+ }) "EndpointManager"
+
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Show Bulk Delete "
+ Key = "EMAllowBulkDelete"
+ Type = "Boolean"
+ DefaultValue = $false
+ Description = "Allow using bulk delete to delete all objects of selected types"
+ }) "EndpointManager"
+
$viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerPanel.xaml") -AddVariables
Set-EMViewPanel $viewPanel
@@ -89,6 +105,8 @@ function Invoke-InitializeModule
Authenticate = { Invoke-EMAuthenticateToMSAL }
AppInfo = (Get-GraphAppInfo "EMAzureApp" "d1ddf0e4-d672-4dae-b554-9d5bdfd93547")
SaveSettings = { Invoke-EMSaveSettings }
+
+ Permissions = @()
})
Add-ViewObject $global:EMViewObject
@@ -166,6 +184,7 @@ function Invoke-InitializeModule
PostImportCommand = { Start-PostImportIntuneBranding @args }
PostGetCommand = { Start-PostGetIntuneBranding @args }
PostExportCommand = { Start-PostExportIntuneBranding @args }
+ PreDeleteCommand = { Start-PreDeleteIntuneBranding @args }
Permissons=@("DeviceManagementApps.ReadWrite.All")
Icon = "Branding"
SkipRemoveProperties = @('Id') # Id is removed by PreImport. Required for default profile
@@ -202,6 +221,7 @@ function Invoke-InitializeModule
ViewID = "IntuneGraphAPI"
PreImportCommand = { Start-PreImportESP @args }
PostExportCommand = { Start-PostExportESP @args }
+ PreDeleteCommand = { Start-PreDeleteEnrollmentRestrictions @args } # Note: Uses same PreDelete as restrictions
QUERYLIST = "`$filter=endsWith(id,'Windows10EnrollmentCompletionPageConfiguration')"
Permissons=@("DeviceManagementServiceConfig.ReadWrite.All")
SkipRemoveProperties = @('Id')
@@ -217,6 +237,7 @@ function Invoke-InitializeModule
QUERYLIST = "`$filter=not endsWith(id,'Windows10EnrollmentCompletionPageConfiguration')"
PostExportCommand = { Start-PostExportEnrollmentRestrictions @args }
PreImportCommand = { Start-PreImportEnrollmentRestrictions @args }
+ PreDeleteCommand = { Start-PreDeleteEnrollmentRestrictions @args }
Permissons=@("DeviceManagementServiceConfig.ReadWrite.All")
SkipRemoveProperties = @('Id')
AssignmentsType = "enrollmentConfigurationAssignments"
@@ -522,13 +543,26 @@ function Invoke-InitializeModule
ImportOrder = 15
GroupId = "TenantAdmin"
})
+
+ Add-ViewItem (New-Object PSObject -Property @{
+ Title = "Health Scripts"
+ Id = "DeviceHealthScripts"
+ ViewID = "IntuneGraphAPI"
+ QUERYLIST = "`$filter=isGlobalScript%20eq%20false" # Looks like filters are not working for deviceHealthScripts
+ API = "/deviceManagement/deviceHealthScripts"
+ PreDeleteCommand = { Start-PreDeleteDeviceHealthScripts @args }
+ Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
+ GroupId = "EndpointAnalytics"
+ Icon = "Report"
+ AssignmentsType = "deviceHealthScriptAssignments"
+ })
}
function Invoke-EMAuthenticateToMSAL
{
$global:EMViewObject.AppInfo = Get-GraphAppInfo "EMAzureApp" "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
Set-MSALCurrentApp $global:EMViewObject.AppInfo
- & $global:msalAuthenticator.Login -Account (?? $global:MSALToken.Account.UserName (Get-Setting "" "LastLoggedOnUser"))
+ & $global:msalAuthenticator.Login -Account (?? $global:MSALToken.Account.UserName (Get-Setting "" "LastLoggedOnUser")) -Permissions $global:EMViewObject.Permissions
}
function Invoke-EMDeactivateView
@@ -566,16 +600,38 @@ function Invoke-EMSaveSettings
Connect-MSALUser -Account $global:MSALToken.Account.Username
Write-Status ""
}
+
+ # Hide/Show Delete button
+ $allowDelete = Get-SettingValue "EMAllowDelete"
+ $global:btnDelete.Visibility = (?: ($allowDelete -eq $true) "Visible" "Collapsed")
+
+ # Hide/Show Delete on Bulk menu
+ $allowBulkDelete = Get-SettingValue "EMAllowBulkDelete"
+ $mnuBulk = $mnuMain.Items | Where Name -eq "EMBulk"
+
+ if($mnuBulk)
+ {
+ $mnuBulkDelete = $mnuBulk.Items | Where Name -eq "mnuBulkDelete"
+ if($mnuBulkDelete)
+ {
+ $mnuBulkDelete.Visibility = (?: ($allowBulkDelete -eq $true) "Visible" "Collapsed")
+ }
+ }
}
function Set-EMViewPanel
{
param($panel)
+
# ToDo: Create View specific pannel and move this to graph
Add-XamlEvent $panel "btnView" "Add_Click" -scriptBlock ([scriptblock]{
Show-GraphObjectInfo
})
+ Add-XamlEvent $panel "btnDelete" "Add_Click" -scriptBlock ([scriptblock]{
+ Remove-GraphObjects
+ })
+
Add-XamlEvent $panel "btnCopy" "Add_Click" -scriptBlock ([scriptblock]{
Copy-GraphObject
})
@@ -603,11 +659,14 @@ function Set-EMViewPanel
})
Invoke-FiterBoxChanged ($panel.FindName("txtFilter"))
-
+
+ $allowDelete = Get-SettingValue "EMAllowDelete"
+ Set-XamlProperty $panel "btnDelete" "Visibility" (?: ($allowDelete -eq $true) "Visible" "Collapsed")
$global:dgObjects.add_selectionChanged({
Set-XamlProperty $this.Parent "btnView" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null) $false $true)
Set-XamlProperty $this.Parent "btnCopy" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null) $false $true)
+ Set-XamlProperty $this.Parent "btnDelete" "IsEnabled" (?: ($global:dgObjects.SelectedItem -eq $null -and $global:curObjectType.AllowDelete -ne $false) $false $true)
})
# ToDo: Move this to the view object
@@ -922,6 +981,15 @@ function Start-PostExportIntuneBranding
}
}
+function Start-PreDeleteIntuneBranding
+{
+ param($obj, $objectType)
+
+ if($obj.isDefaultProfile -eq $true)
+ {
+ @{ "Delete" = $false }
+ }
+}
#endregion
@@ -1613,6 +1681,16 @@ function Start-PreImportEnrollmentRestrictions
Remove-Property $obj "Id"
}
}
+
+function Start-PreDeleteEnrollmentRestrictions
+{
+ param($obj, $objectType)
+
+ if($obj.Priority -eq 0)
+ {
+ @{ "Delete" = $false }
+ }
+}
#endregion
#region ScopeTags
@@ -1633,6 +1711,20 @@ function Start-PreImportAssignmentsAutoPilot
}
#endregion
+#region Health Scripts
+
+function Start-PreDeleteDeviceHealthScripts
+{
+ param($obj, $objectType)
+
+ if($obj.IsGlobal -eq $true)
+ {
+ @{ "Delete" = $false }
+ }
+}
+
+#endregion
+
#region Generic functions
function Save-EMDefaultPolicy
diff --git a/Extensions/EndpointManagerInfo.psm1 b/Extensions/EndpointManagerInfo.psm1
index 3510ae8..e674a19 100644
--- a/Extensions/EndpointManagerInfo.psm1
+++ b/Extensions/EndpointManagerInfo.psm1
@@ -10,7 +10,7 @@ This module is for the Endpoint Info View. It shows read-only objects in Intune
#>
function Get-ModuleVersion
{
- '3.1.0'
+ '3.1.1'
}
function Invoke-InitializeModule
@@ -27,6 +27,7 @@ function Invoke-InitializeModule
Authenticate = { Invoke-EMInfoAuthenticateToMSAL }
AppInfo = (Get-GraphAppInfo "EM" "d1ddf0e4-d672-4dae-b554-9d5bdfd93547")
SaveSettings = { Invoke-EMSaveSettings }
+ Permissions = @()
})
Add-ViewObject $global:EMInfoViewObject
@@ -80,7 +81,7 @@ function Invoke-InitializeModule
ShowButtons = @("View")
Permissons=@("DeviceManagementServiceConfig.ReadWrite.All")
})
-
+
}
function Invoke-EMInfoActivatingView
@@ -99,6 +100,6 @@ function Invoke-EMInfoAuthenticateToMSAL
$usr = (?? $global:MSALToken.Account.UserName (Get-Setting "" "LastLoggedOnUser"))
if($usr)
{
- & $global:msalAuthenticator.Login -Account $usr
+ & $global:msalAuthenticator.Login -Account $usr -Permissions $global:EMInfoViewObject.Permissions
}
}
\ No newline at end of file
diff --git a/Extensions/MSALAuthentication.psm1 b/Extensions/MSALAuthentication.psm1
index 84fb743..92a4d58 100644
--- a/Extensions/MSALAuthentication.psm1
+++ b/Extensions/MSALAuthentication.psm1
@@ -10,7 +10,7 @@ This module manages Authentication for the application with MSAL. It is also res
#>
function Get-ModuleVersion
{
- '3.0.0'
+ '3.0.1'
}
$global:msalAuthenticator = $null
@@ -59,6 +59,14 @@ function Invoke-InitializeModule
Description = "Default permissions of the selected app will be used when logging on. Some objects might not be accessable"
}) "MSAL"
+ Add-SettingsObject (New-Object PSObject -Property @{
+ Title = "Add Azure Role Read permissions"
+ Key = "AzureADRoleRead"
+ Type = "Boolean"
+ DefaultValue = $false
+ Description = "Request Azure AD Role read permission when getting the token. This can be use to resolve the SIDs to Azure Roles for the wids property on the Access Token. Note: This might trigger a consent prompt"
+ }) "MSAL"
+
Add-MSALPrereq
#$script:MSALDLLMissing = $true #!!!!
@@ -75,6 +83,7 @@ function Get-MSALAuthenticationObject
Logout = { Disconnect-MSALUser }
ProfilePicture = { Get-MSALProfileEllipse @args }
ShowErrors = { Show-MSALError }
+ Permissions = @("openid","profile","email","User.ReadWrite.All","Group.ReadWrite.All") #"RoleManagement.Read.Directory"
}
}
@@ -511,7 +520,9 @@ function Connect-MSALUser
[switch]
$Interactive,
- $Account
+ $Account,
+
+ $Permissions = @() # Addidional permissions required by the current view object
)
# No login during first time the app is started
@@ -559,8 +570,29 @@ function Connect-MSALUser
}
else
{
- $Scopes = [string[]]$global:PermissionScope
+ #$Scopes = [string[]]$global:PermissionScope
+ $reqScopes = [string[]]$global:msalAuthenticator.Permissions
$useDefaultPermissions = $false
+
+ $resolveRoles = ((Get-SettingValue "AzureADRoleRead" $false) -eq $true)
+
+ if($resolveRoles -and $global:msalAuthenticator.Permissions -notcontains "RoleManagement.Read.Directory")
+ {
+ # Adds the required permission for reading AAD directory roles
+ $reqScopes += "RoleManagement.Read.Directory"
+ }
+
+ if($Permissions.Count -gt 0)
+ {
+ $script:curViewPermissions = $Permissions
+ }
+ $reqScopes += $script:curViewPermissions
+
+ foreach($tmpScope in $script:curViewPermissions)
+ {
+ if($reqScopes -notcontains $tmpScope) { $reqScopes += $tmpScope }
+ }
+ $Scopes = [String[]]$reqScopes
}
$global:MSALApp = Get-MSALApp $global:appObj
@@ -645,14 +677,14 @@ function Connect-MSALUser
#AADSTS65001
if($script:authenticationFailure.Classification -eq "ConsentRequired")
{
- $Scopes = [string[]]$global:PermissionScope
+ $Scopes = [string[]]$reqScopes
}
else
{
- if($useDefaultPermissions -eq $false -and $authResult -and ($global:PermissionScope | measure).Count -gt 0 -and $global:promptConsentRequested -notcontains $authResult.TenantId)
+ if($useDefaultPermissions -eq $false -and $authResult -and ($reqScopes | measure).Count -gt 0 -and $global:promptConsentRequested -notcontains $authResult.TenantId)
{
$missingScopes = @()
- foreach($scope in $global:PermissionScope)
+ foreach($scope in $reqScopes)
{
$tmpScope = $scope.Split('/')[-1]
if($tmpScope -eq ".default") { continue }
@@ -1270,7 +1302,7 @@ function Get-MSALProfileEllipse
$dg = [System.Windows.Controls.DataGrid]::new()
$dg.ItemsSource = ($tokenArr | Select Name, Value)
- Show-ModalForm "Token info" $dg
+ Show-ModalForm "Token info" $dg
}
Add-XamlEvent $tmpObj "lnkAccessTokenInfo" "add_Click" {
diff --git a/Extensions/MSGraph.psm1 b/Extensions/MSGraph.psm1
index bc74f5f..5349315 100644
--- a/Extensions/MSGraph.psm1
+++ b/Extensions/MSGraph.psm1
@@ -10,7 +10,7 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob
#>
function Get-ModuleVersion
{
- '3.1.0'
+ '3.1.1'
}
$global:MSGraphGlobalApps = @(
@@ -697,19 +697,28 @@ function Show-GraphBulkExportForm
Add-GraphExportExtensions $script:exportForm 0
- $script:lstObjectsToExport = $script:exportForm.FindName("lstObjectsToExport")
- if($script:lstObjectsToExport)
- {
- $script:lstObjectsToExport.ItemsSource = $script:exportObjects
+ $column = Get-GridCheckboxColumn "Selected"
+ $global:dgObjectsToExport.Columns.Add($column)
- Add-XamlEvent $script:exportForm "chkCheckAll" "add_click" ({
- foreach($item in $script:exportObjects)
- {
+ $column.Header.IsChecked = $true # All items are checked by default
+ $column.Header.add_Click({
+ foreach($item in $global:dgObjectsToExport.ItemsSource)
+ {
$item.Selected = $this.IsChecked
}
- $script:lstObjectsToExport.Items.Refresh()
- })
- }
+ $global:dgObjectsToExport.Items.Refresh()
+ }
+ )
+
+ # Add Object type column
+ $binding = [System.Windows.Data.Binding]::new("Title")
+ $column = [System.Windows.Controls.DataGridTextColumn]::new()
+ $column.Header = "Object type"
+ $column.IsReadOnly = $true
+ $column.Binding = $binding
+ $global:dgObjectsToExport.Columns.Add($column)
+
+ $global:dgObjectsToExport.ItemsSource = $script:exportObjects
Add-XamlEvent $script:exportForm "btnClose" "add_click" ({
$script:exportForm = $null
@@ -739,11 +748,11 @@ 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")
- $objects = @(Get-GraphObjects -Url $url -property $objectType.ViewProperties -objectType $objectType)
+ $objects = @(Get-GraphObjects -Url $url -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
foreach($obj in $objects)
{
- Write-Status "Export $($item.Title): $((Get-GraphObjectName $obj))" -Force
- Export-GraphObject $obj.Object $item.ObjectType $folder
+ Write-Status "Export $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force
+ Export-GraphObject $obj.Object $item.ObjectType $folder
}
Save-Setting "" "LastUsedFullPath" $folder
}
@@ -876,19 +885,36 @@ function Show-GraphBulkImportForm
Add-GraphImportExtensions $script:importForm 0
- $script:lstObjectsToImport = $script:importForm.FindName("lstObjectsToImport")
- if($script:lstObjectsToImport)
- {
- $script:lstObjectsToImport.ItemsSource = $script:importObjects
+ $column = Get-GridCheckboxColumn "Selected"
+ $global:dgObjectsToImport.Columns.Add($column)
- Add-XamlEvent $script:importForm "chkCheckAll" "add_click" ({
- foreach($item in $script:importObjects)
- {
+ $column.Header.IsChecked = $true # All items are checked by default
+ $column.Header.add_Click({
+ foreach($item in $global:dgObjectsToImport.ItemsSource)
+ {
$item.Selected = $this.IsChecked
}
- $script:lstObjectsToImport.Items.Refresh()
- })
- }
+ $global:dgObjectsToImport.Items.Refresh()
+ }
+ )
+
+ # Add Object type column
+ $binding = [System.Windows.Data.Binding]::new("Title")
+ $column = [System.Windows.Controls.DataGridTextColumn]::new()
+ $column.Header = "Object type"
+ $column.IsReadOnly = $true
+ $column.Binding = $binding
+ $global:dgObjectsToImport.Columns.Add($column)
+
+ # Add Order column
+ $binding = [System.Windows.Data.Binding]::new("ObjectType.ImportOrder")
+ $column = [System.Windows.Controls.DataGridTextColumn]::new()
+ $column.Header = "Import order"
+ $column.IsReadOnly = $true
+ $column.Binding = $binding
+ $global:dgObjectsToImport.Columns.Add($column)
+
+ $global:dgObjectsToImport.ItemsSource = $script:importObjects
Add-XamlEvent $script:importForm "btnClose" "add_click" ({
$script:importForm = $null
@@ -934,6 +960,11 @@ function Show-GraphBulkImportForm
{
[System.Windows.MessageBox]::Show("No objects were imported. Verify folder and exported files", "Error", "OK", "Error")
}
+ else
+ {
+ Show-GraphObjects
+ Write-Status ""
+ }
})
if((Get-XamlProperty $script:importForm "txtImportPath" "Text"))
@@ -994,6 +1025,113 @@ function Add-GraphImportExtensions
}
}
+function Show-GraphBulkDeleteForm
+{
+ $script:deleteForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkDeleteForm.xaml") -AddVariables
+ if(-not $script:deleteForm) { return }
+
+ $script:deleteObjects = @()
+ foreach($objType in $global:lstMenuItems.ItemsSource)
+ {
+ if(-not $objType.Title) { continue }
+
+ if($objType.ShowButtons -is [Object[]] -and $objType.ShowButtons -notcontains "Delete") { continue }
+
+ $script:deleteObjects += New-Object PSObject -Property @{
+ Title = $objType.Title
+ Selected = $false
+ ObjectType = $objType
+ }
+ }
+
+ $column = Get-GridCheckboxColumn "Selected"
+ $global:dgBulkDeleteObjects.Columns.Add($column)
+
+ $column.Header.IsChecked = $false # All items are NOT checked by default
+ $column.Header.add_Click({
+ foreach($item in $global:dgBulkDeleteObjects.ItemsSource)
+ {
+ $item.Selected = $this.IsChecked
+ }
+ $global:dgBulkDeleteObjects.Items.Refresh()
+ }
+ )
+
+ # Add title column
+ $binding = [System.Windows.Data.Binding]::new("Title")
+ $column = [System.Windows.Controls.DataGridTextColumn]::new()
+ $column.Header = "Title"
+ $column.IsReadOnly = $true
+ $column.Binding = $binding
+ $global:dgBulkDeleteObjects.Columns.Add($column)
+
+ $ocList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:deleteObjects))
+ $global:dgBulkDeleteObjects.ItemsSource = [System.Windows.Data.CollectionViewSource]::GetDefaultView($ocList)
+
+ Add-XamlEvent $script:deleteForm "btnClose" "add_click" ({
+ $script:deleteForm = $null
+ Show-ModalObject
+ })
+
+ Add-XamlEvent $script:deleteForm "btnDelete" "add_click" ({
+
+ $selCount = (($global:dgBulkDeleteObjects.ItemsSource | Where Selected -eq $true) | measure).Count
+
+ if($selCount -eq 0)
+ {
+ [System.Windows.MessageBox]::Show("No object types selected`n`nSelect types you want to delete", "Error", "OK", "Error")
+ return
+ }
+
+ if(([System.Windows.MessageBox]::Show("Are you sure you want to delete all objects of the selected type(s)?`n`n$selCount type(s) selected", "Delete Objects?", "YesNo", "Warning")) -ne "Yes")
+ {
+ return
+ }
+
+ Write-Status "Delete objects" -Block
+ Write-Log "****************************************************************"
+ Write-Log "Start bulk delete"
+ Write-Log "****************************************************************"
+
+ foreach($item in ($global:dgBulkDeleteObjects.ItemsSource | Where Selected -eq $true))
+ {
+ Write-Log "----------------------------------------------------------------"
+ Write-Log "Delete $($item.ObjectType.Title) objects"
+ Write-Log "----------------------------------------------------------------"
+
+ $url = $item.ObjectType.API
+ if($item.ObjectType.QUERYLIST)
+ {
+ $url = "$($url.Trim())?$($item.ObjectType.QUERYLIST.Trim())"
+ }
+
+ try
+ {
+ Write-Status "Get $($item.Title) objects" -Force
+ $objects = @(Get-GraphObjects -Url $url -property $item.ObjectType.ViewProperties -objectType $item.ObjectType)
+ foreach($obj in $objects)
+ {
+ Write-Status "Delete $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force
+ Remove-GraphObject $obj.Object $obj.ObjectType $folder
+ }
+ }
+ catch
+ {
+ Write-LogError "Failed when deleting $($item.Title) objects" $_.Exception
+ }
+ }
+
+ Write-Log "****************************************************************"
+ Write-Log "Bulk delete finished"
+ Write-Log "****************************************************************"
+ Show-GraphObjects
+ Write-Status ""
+ })
+
+
+ Show-ModalForm "Bulk Delete" $script:deleteForm -HideButtons
+}
+
function Get-GraphFileObjects
{
param($path, $Exclude = @("*_settings.json","*_assignments.json"), $SelectedStatus = $true, $ObjectType = $global:curObjectType)
@@ -1788,6 +1926,82 @@ function Import-GraphObject
$newObj
}
+function Remove-GraphObjects
+{
+ $objectsToDelete = @()
+ if(($global:dgObjects.ItemsSource | Where IsSelected -eq $true).Count -gt 0)
+ {
+ # Delete checked items
+ $objectsToDelete += ($global:dgObjects.ItemsSource | Where IsSelected -eq $true)
+ }
+ elseif($global:dgObjects.SelectedItem)
+ {
+ # Delete the selected item
+ $objectsToDelete += $global:dgObjects.SelectedItem
+ }
+
+ if($objectsToDelete.Count -eq 0)
+ {
+ [System.Windows.MessageBox]::Show("No object selected`n`nSelect items you want to delete", "Error", "OK", "Error")
+ return
+ }
+
+ if(([System.Windows.MessageBox]::Show("Are you sure you want to delete $($objectsToDelete.Count) $($global:curObjectType.Title) object(s)?", "Delete Objects?", "YesNo", "Warning")) -ne "Yes")
+ {
+ return
+ }
+
+ foreach($tmpObj in $objectsToDelete)
+ {
+ Remove-GraphObject $tmpObj.Object $tmpObj.ObjectType
+ }
+
+ Show-GraphObjects
+ Write-Status ""
+}
+
+function Remove-GraphObject
+{
+ param($objToRemove, $objectType)
+
+ $strAPI = $null
+ if($objectType.PreDeleteCommand)
+ {
+ $ret = & $objectType.PreDeleteCommand $objToRemove $objectType
+ if($ret -is [HashTable])
+ {
+ if($ret.ContainsKey("Delete") -and $ret["Delete"] -eq $false)
+ {
+ # Delete handled manually or aborted
+ return $false
+ }
+
+ if($ret.ContainsKey("API"))
+ {
+ $strAPI = $ret["API"]
+ }
+ }
+ }
+
+ if($strAPI)
+ {
+ $api = $strAPI
+ }
+ elseif($objectType.DELETEAPI)
+ {
+ $api = $objectType.DELETEAPI
+ }
+ else
+ {
+ $api = $objectType.API
+ }
+
+ Write-Status "Delete $((Get-GraphObjectName $objToRemove $objectType))"
+ $strAPI = ($api + "/$($objToRemove.Id)")
+ Write-Log "Delete $($objectType.Title) object $((Get-GraphObjectName $objToRemove $objectType))"
+ Invoke-GraphRequest -Url $strAPI -HttpMethod "DELETE" -ODataMetadata "none"
+}
+
function Copy-GraphObject
{
if(-not $dgObjects.SelectedItem)
@@ -1940,14 +2154,25 @@ function Add-GraphBulkMenu
$menuItem = [System.Windows.Controls.MenuItem]::new()
$menuItem.Header = "_Bulk"
$menuItem.Name = "EMBulk"
+
$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 "EMAllowBulkDelete"
+ # 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
$mnuMain.Items.Insert(1,$menuItem) | Out-Null
}
\ No newline at end of file
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 596e814..e3b3982 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,5 +1,25 @@
# Release Notes
+## 3.1.2 - 2021-06-20
+
+**New features**
+
+- Delete and Bulk Delete - Delete selected items or delete ALL items of selected object types
+
+ **Note:** This must be enabled in the settings. They are not visible by default.
+
+ **WARNING:** Use this carefully! It will delete profiles and policies in Intune.
+
+- Support for new object Health Scripts
+
+- Object permissions is now handled by ViewObject and authentication provider. This is to support future view extensions.
+
+**Fixes**
+
+* Azure Role Read permission can be disabled in settings
+* Minor UI changes e.g. List Boxes for bulk Import/Export changed to DataGrid
+* Minor bulk export fixes
+
## 3.1.1 - 2021-06-16
**New features**
diff --git a/Xaml/BulkDeleteForm.xaml b/Xaml/BulkDeleteForm.xaml
new file mode 100644
index 0000000..be523e6
--- /dev/null
+++ b/Xaml/BulkDeleteForm.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xaml/BulkExportForm.xaml b/Xaml/BulkExportForm.xaml
index 610a3d4..92b176a 100644
--- a/Xaml/BulkExportForm.xaml
+++ b/Xaml/BulkExportForm.xaml
@@ -76,7 +76,10 @@
-
+
+
+
+
diff --git a/Xaml/BulkImportForm.xaml b/Xaml/BulkImportForm.xaml
index d414891..d9c420f 100644
--- a/Xaml/BulkImportForm.xaml
+++ b/Xaml/BulkImportForm.xaml
@@ -90,29 +90,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
diff --git a/Xaml/EndpointManagerPanel.xaml b/Xaml/EndpointManagerPanel.xaml
index 090bc37..529c561 100644
--- a/Xaml/EndpointManagerPanel.xaml
+++ b/Xaml/EndpointManagerPanel.xaml
@@ -42,6 +42,7 @@
+
diff --git a/Xaml/Icons/Report.xaml b/Xaml/Icons/Report.xaml
new file mode 100644
index 0000000..a3d66a8
--- /dev/null
+++ b/Xaml/Icons/Report.xaml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file