Files
macOS_IntuneManagement/PSExtensionsHost.ps1
Micke b4c737dd6d Removed support for AzureRM
Az module is required for Native Azure objects (Conditional access, Azure branding and MDM/MAM settings)
Fixed limit on Conditional Access objects
Remove properties before import (date etc.)
Added WIP policies
Added support for installing Intune module for user only
2021-02-03 12:50:22 +11:00

1269 lines
36 KiB
PowerShell

<#
.SYNOPSIS
Script for hosting PowerShell extenstions
.DESCRIPTION
This is a foundation UI that act as a host for extensions. The scrtipt itself has no functionallity.
Extension functionallity:
Menu handling
Add any type of objects to a data grid
Logging
UI
.EXAMPLE
PSExtensionsHost -Title "Intune/Azure PowerShell Management" -ShowConsoleWindow
This will set the windows title and keep the command visible for debug resouns
.NOTES
Author: Mikael Karlsson
Date: 2019-06-01
#>
[CmdletBinding(SupportsShouldProcess=$True)]
param(
[string]
$Title = "Intune/Azure PowerShell Management",
[switch]
$ShowConsoleWindow
)
#####################################################################################################
#
# Global functions
#
#####################################################################################################
function global:Write-Log
{
param($Text, $type = 1)
if($script:logFailed -eq $true) { return }
if(-not $global:logFile) { $global:logFile = Get-SettingValue "LogFile" ([IO.Path]::Combine($PSScriptRoot,"PSExtensionsHost.Log")) }
try
{
$logPath = [IO.Path]::GetDirectoryName($global:logFile)
if(-not (Test-Path $logPath)) { mkdir -Path $logPath -Force -ErrorAction SilentlyContinue | Out-Null }
}
catch
{
$script:logFailed = $true
return
}
$date = Get-Date
if($global:PSCommandPath)
{
$fileObj = [System.IO.FileInfo]$global:PSCommandPath
}
else
{
$fileObj = [System.IO.FileInfo]$PSCommandPath
}
$timeStr = "$($date.ToString(""HH"")):$($date.ToString(""mm"")):$($date.ToString(""ss"")).000+000"
$dateStr = "$($date.ToString(""MM""))-$($date.ToString(""dd""))-$($date.ToString(""yyyy""))"
$logOut = "<![LOG[$Text]LOG]!><time=""$timeStr"" date=""$dateStr"" component=""$($fileObj.BaseName)"" context="""" type=""$type"" thread=""$PID"" file=""$($fileObj.BaseName)"">"
if($type -eq 2)
{
Write-Warning $Text
}
elseif($type -eq 3)
{
$host.ui.WriteErrorLine($Text)
}
else
{
write-host $Text
}
try
{
out-file -filePath $global:logFile -append -encoding "ASCII" -inputObject $logOut
}
catch { }
}
function global:Write-LogError
{
param($Text, $Exception)
if($Text -and $Exception.message)
{
$Text += " Exception: $($Exception.message)"
}
Write-Log $Text 3
}
function global:Write-Status
{
param($Text, [switch]$SkipLog)
$txtInfo.Content = $Text
if($text)
{
$grdStatus.Visibility = "Visible"
if($SkipLog -ne $true) { Write-Log $text }
}
else
{
$grdStatus.Visibility = "Collapsed"
}
[System.Windows.Forms.Application]::DoEvents()
}
function global:Show-AboutDialog
{
[xml]$xaml = @"
<Window $wpfNS Title="About" SizeToContent="Height" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" Width="300">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Height="20" FontWeight="Bold" Text="$($window.Title)" Margin="0,5,0,5" />
<TextBlock Grid.Row="1" Text="(c) 2019 Mikael Karlsson" Margin="0,5,0,5" />
<TextBlock Grid.Row="2">
See
<Hyperlink Name="linkSource" NavigateUri="https://github.com/Micke-K/IntuneManagement">
GitHub
</Hyperlink> for more information
</TextBlock>
<TextBlock Grid.Row="3" Text="Loaded modules:" Margin="0,5,0,5" />
<ListBox Name="lstModules" SelectionMode="Single" Grid.Row="4" Height="100" Grid.IsSharedSizeScope='True'>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="NameColumn"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.Column='0' Margin="5,0,0,0" />
<TextBlock Text="{Binding Version}" Grid.Column='1' Margin="5,0,0,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="5" HorizontalAlignment="Right" Name="btnOk" Padding="5,0,5,0" Margin="0,5,0,0" Width="60">OK</Button>
</Grid>
</Window>
"@
$script:dlgAbout = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
$btnOk = $dlgAbout.FindName("btnOk")
$lstModules = $dlgAbout.FindName("lstModules")
$linkSource = $dlgAbout.FindName("linkSource")
$lstModules.ItemsSource = Get-Module | Where { $_.ModuleBase -like "$($global:PSScriptRoot)*" } | Sort -Property Name
$btnOk.Add_Click({
$script:dlgAbout.Close()
})
$linkSource.Add_RequestNavigate({
[System.Diagnostics.Process]::Start($_.Uri.AbsoluteUri)
$_.Handled = $true
})
$script:dlgAbout.ShowDialog() | Out-Null
$global:menuObjects | ForEach-Object {
# Clear selection in all menu sections - So it can be pressed again
$PSItem.MenuListBox.SelectedItem = $null
}
}
function global:Add-XamlVariables
{
param($xaml)
# Generate a global variable for each object with Name property set
# Ref: https://learn-powershell.net/2014/08/10/powershell-and-wpf-radio-button/
$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
New-Variable -Name $_.Name -Value $Window.FindName($_.Name) -Force -Scope Global
}
}
function global:Remove-InvalidFileNameChars
{
param($Name)
$re = "[{0}]" -f [RegEx]::Escape(([IO.Path]::GetInvalidFileNameChars() -join ''))
$Name = $Name -replace $re
$Name = $Name -replace "[]]", ""
$Name = $Name -replace "[[]", ""
return $Name
}
function global:Remove-ObjectProperty
{
param($obj, $property)
if(-not $obj -or -not $property) { return }
if(($obj | GM -MemberType NoteProperty -Name $property))
{
$obj.PSObject.Properties.Remove($property)
}
}
function global:Show-InputDialog
{
param(
$FormTitle = "Input",
$FormText,
$DefaultValue)
[xml]$xaml = @"
<Window $wpfNS
Title="$FormTitle" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Column="1">$FormText</Label>
<TextBox Name="txtValue" Grid.Column="1" Grid.Row="1" MinWidth="250">$DefaultValue</TextBox>
<WrapPanel Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Right" Margin="0,15,0,0">
<Button IsDefault="True" Name="btnOk" MinWidth="60" Margin="0,0,10,0">_Ok</Button>
<Button IsCancel="True" Name="btnCancel" MinWidth="60">_Cancel</Button>
</WrapPanel>
</Grid>
</Window>
"@
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$script:inputBox = [Windows.Markup.XamlReader]::Load($reader)
$script:txtValue = $script:inputBox.FindName("txtValue")
$btnOk = $script:inputBox.FindName("btnOk")
$btnCancel = $script:inputBox.FindName("btnCancel")
$inputBox.Add_ContentRendered({
$script:txtValue.SelectAll();
$script:txtValue.Focus();
})
$script:InputDialogValue = ""
$btnOk.Add_Click({
$script:inputBox.Close()
})
$btnCancel.Add_Click({
$script:txtValue.Text =""
$script:inputBox.Close()
})
$inputBox.ShowDialog() | Out-null
return $script:txtValue.Text
}
function global:Set-ObjectGrid
{
param( $obj )
if($obj)
{
$grdObject.Children.Add($obj)
$grdObject.Visibility = "Visible"
}
else
{
$grdObject.Children.Clear()
$grdObject.Visibility = "Collapsed"
}
[System.Windows.Forms.Application]::DoEvents()
}
function global:Clear-Objects
{
$global:txtFormTitle.Text = ""
$global:txtFormTitle.Visibility = "Collapsed"
$spSubMenu.Visibility = "Collapsed"
$spSubMenu.Children.Clear()
$grdObject.Children.Clear()
$dgObjects.ItemsSource = $null
Set-ObjectGrid
[System.Windows.Forms.Application]::DoEvents()
}
function global:Show-SubMenu
{
$spSubMenu.Visibility = "Visible"
[System.Windows.Forms.Application]::DoEvents()
}
function global:Get-Folder
{
param($path = $env:temp)
if($global:useDefaultFolderDialog -ne $true)
{
try
{
if($global:WindowsAPICodePackLoaded -eq $false)
{
$apiCodec = Join-Path $PSScriptRoot "Microsoft.WindowsAPICodePack.Shell.dll"
if([IO.File]::Exists($apiCodec))
{
Add-Type -Path $apiCodec | Out-Null
$global:WindowsAPICodePackLoaded = $true
}
else
{
}
}
else
{
}
$dlgCOFD = New-Object Microsoft.WindowsAPICodePack.Dialogs.CommonOpenFileDialog
}
catch {
}
}
if($dlgCOFD -and $global:useDefaultFolderDialog -ne $true)
{
$dlgCOFD.EnsureReadOnly = $true
$dlgCOFD.IsFolderPicker = $true
$dlgCOFD.AllowNonFileSystemItems = $false
$dlgCOFD.Multiselect = $false
$dlgCOFD.Title = "Please select the destination directory"
if($path -and (Test-Path $path))
{
$dlgCOFD.InitialDirectory = $path
}
if($dlgCOFD.ShowDialog($window) = [Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialogResult]::Ok)
{
$dlgCofd.FileName
}
}
else
{
$global:useDefaultFolderDialog = $true
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles()
$dlgFBD = New-Object System.Windows.Forms.FolderBrowserDialog
$dlgFBD.SelectedPath = "C:\"
$dlgFBD.ShowNewFolderButton = $false
$dlgFBD.Description = "Select a directory"
if($dlgFBD.ShowDialog() -eq "OK")
{
$dlgFBD.SelectedPath
}
$dlgFBD.Dispose()
}
}
#region Reg functions
########################################################################
#
# Reg functions
#
########################################################################
function global:Save-RegSetting
{
param($SubPath, $Key, $Value, $Type = "String")
$regPath = Get-RegPath $SubPath
if((Test-Path $regPath) -eq $false)
{
New-Item (Get-RegPath $SubPath) -ErrorAction SilentlyContinue
}
New-ItemProperty -Path $regPath -Name $Key -Value $Value -Type $Type -Force | Out-Null
}
function global:Get-RegSetting
{
param($SubPath, $Key, $defautValue)
try
{
$val = Get-ItemPropertyValue -Path (Get-RegPath $SubPath) -Name $Key -ErrorAction SilentlyContinue
}
catch { }
if(-not $val)
{
$defautValue
}
else
{
$val
}
}
function global:Get-RegPath
{
param($SubPath)
$path = "HKCU:\Software\IntunePSTools"
if($SubPath)
{
$path = $path + "\" + $SubPath
}
$path
}
#endregion
#region Setting functions
########################################################################
#
# Settings functions
#
########################################################################
function global:Add-SettingTextBox
{
param($id, $value)
$xaml = @"
<TextBox $wpfNS Name="$($id)" Tag="$title">$value</TextBox>
"@
return [Windows.Markup.XamlReader]::Parse($xaml)
}
function global:Add-SettingCheckBox
{
param($id, $value)
$tmpValue = ($value -eq $true -or $value -eq "true").ToString().ToLower()
$xaml = @"
<CheckBox $wpfNS Name="$($id)" IsChecked="$($tmpValue)" />
"@
return [Windows.Markup.XamlReader]::Parse($xaml)
}
function global:Add-SettingFolder
{
param($id, $value)
$xaml = @"
<Grid $wpfNS HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Name="$($id)">$value</TextBox>
<Button Grid.Column="2" Name="browse_$($id)" Padding="5,0,5,0" Width="50">...</Button>
</Grid>
"@
$obj = [Windows.Markup.XamlReader]::Parse($xaml)
$btnBrowse = $obj.FindName("browse_$($id)")
$txtObj = $obj.FindName($id)
if($btnBrowse)
{
$btnBrowse.Tag = $txtObj
$btnBrowse.Add_Click({
$folder = Get-Folder $this.Tag.Text
if($folder) { $this.Tag.Text = $folder }
})
}
return $obj
}
function global:Add-SettingValue
{
param($settingValue)
$id = "id_" + [Guid]::NewGuid().ToString('n')
$value = Get-SettingValue $settingValue.Key
if($settingValue.Type -eq "folder")
{
$settingObj = Add-SettingFolder $id $value
}
elseif($settingValue.Type -eq "Boolean")
{
$settingObj = Add-SettingCheckBox $id $value
}
else
{
$settingObj = Add-SettingTextBox $id $value
}
$descriptionInfo = ""
if($settingValue.Description)
{
$descriptionInfo = "<Rectangle Style=`"{DynamicResource InfoIcon}`" ToolTip=`"$($settingValue.Description)`" Margin=`"5,0,0,0`" />"
}
$xaml = @"
<Border Margin="0,5,0,0" $wpfNS>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Margin="5,0,5,0">
<TextBlock Text="$($settingValue.Title)" VerticalAlignment="Center"/>
$descriptionInfo
</StackPanel>
<!--
<TextBlock Text="$($settingValue.Title)" VerticalAlignment="Center" Margin="5,0,0,0" />
-->
<Border Grid.Column="2" Name="border_$($id)" />
</Grid>
</Border>
"@
$newSetting = [Windows.Markup.XamlReader]::Parse($xaml)
if($newSetting)
{
$spSettings.AddChild($newSetting)
$tmpObj = $newSetting.FindName("border_$($id)")
$tmpObj.Child = $settingObj
$ctrl = $settingObj.FindName($id)
$global:settingControls += $ctrl
if(($settingValue | GM -MemberType NoteProperty -Name "Control"))
{
$settingValue.Control = $ctrl
}
else
{
$settingValue | Add-Member -MemberType NoteProperty -Name "Control" -Value $ctrl
}
}
}
function global:Add-SettingTitle
{
param($title, $marginTop = "0")
$xaml = @"
<TextBlock $wpfNS Text="$title" Background="{DynamicResource TitleBackgroundColor}" FontWeight="Bold" Padding="5" Margin="0,$marginTop,0,0" />
"@
$global:spSettings.Children.Add([Windows.Markup.XamlReader]::Parse($xaml))
}
function global:Save-Setting
{
foreach($ctrl in $global:settingControls)
{
Write-Host "$($ctrl.Text) $($ctrl.Tag)"
}
}
function Show-SettingsForm
{
$settingsStr = @"
<Grid $wpfNS HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Name="spSettings" Grid.Column="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.IsSharedSizeScope='True' Margin="0">
</StackPanel>
<Button Grid.Row="1" HorizontalAlignment="Right" Name="btnSave" Width="100">Save</Button>
</Grid>
"@
$global:settingControls = @()
$settingsForm = [Windows.Markup.XamlReader]::Parse($settingsStr)
$global:spSettings = $settingsForm.FindName("spSettings")
$btnSave = $settingsForm.FindName("btnSave")
$btnSave.Add_Click({
Save-AllSettings
})
$tmp = $global:appSettingSections | Where Id -eq "General"
if($tmp.Values.Count -gt 0)
{
Add-SettingTitle $tmp.Title
foreach($settingObj in $tmp.Values)
{
Add-SettingValue $settingObj
}
}
foreach($section in ($global:appSettingSections | Where Id -ne "General" | Sort -Property Title))
{
if($section.Values.Count -eq 0) { continue }
Add-SettingTitle $section.Title 5
foreach($settingObj in $section.Values)
{
Add-SettingValue $settingObj
}
}
Set-ObjectGrid $settingsForm
}
function global:Get-Setting
{
foreach($ctrl in $global:settingControls)
{
Write-Host "$($ctrl.Text) $($ctrl.Tag)"
}
}
function global:Add-DefaultSettings
{
$global:appSettingSections = @()
$global:appSettingSections += (New-Object PSObject -Property @{
Title = "General"
Id = "General"
Values = @()
})
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Log file"
Key = "LogFile"
Type = "File"
}) "General"
Add-SettingsObject (New-Object PSObject -Property @{
Title = "Max log file size"
Key = "LogFileSize"
Type = "Int"
DefaultValue = 1024
}) "General"
}
function global:Add-SettingsObject
{
param($obj, $section)
$section = $global:appSettingSections | Where Id -eq $section
if(-not $section) { return }
$section.Values += $obj
}
function global:Save-AllSettings
{
foreach($section in $global:appSettingSections)
{
foreach($settingObj in $section.Values)
{
if($settingObj.Control.GetType().Name -eq "TextBox")
{
$value = $settingObj.Control.Text
if($settingObj.Type -eq "Int")
{
try
{
$value = [int]$value
}
catch
{
# Log or set invalid
$value = $settingObj.Value
}
}
}
elseif($settingObj.Control.GetType().Name -eq "CheckBox")
{
$value = $settingObj.Control.IsChecked
}
if($value)
{
Save-RegSetting $settingObj.SubPath $settingObj.Key $value
}
}
}
}
function global:Get-SettingValue
{
param($Key, $defaultValue)
foreach($section in $global:appSettingSections)
{
$settingObj = $section.Values | Where Key -eq $Key
if($settingObj) { break }
}
if(-not $defaultValue) { $defaultValue = $settingObj.DefaultValue }
$value = Get-RegSetting $settingObj.SubPath $settingObj.Key $defaultValue
if($value)
{
if($settingObj.Type -eq "Boolean")
{
$value = $value -eq $true -or $value -eq "true"
}
elseif($settingObj.Type -eq "Boolean")
{
try
{
$value = [int]$value
}
catch
{
if($settingObj.DefaultValue)
{
try
{
$value = [int]$settingObj.DefaultValue
}
catch { }
}
}
}
# Keep last read value
if(($settingObj | GM -MemberType NoteProperty -Name "Value"))
{
$settingObj.Value = $value # Keep last read value
}
else
{
$settingObj | Add-Member -MemberType NoteProperty -Name "Value" -Value $value
}
}
$value
}
#endregion
#region Menu functions
#####################################################################################################
#
# Menu functions
#
#####################################################################################################
function global:Add-MenuSection
{
param($menuSection)
$id = [Guid]::NewGuid().ToString('n')
[xml]$menuXml = @"
<StackPanel $wpfNS Name="Id_sp_$id" Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0,0,0,0">
<Label Content="$($menuSection.Title)" FontWeight="Bold" Margin="0,0,0,0" Background="{DynamicResource TitleBackgroundColor}" />
<ListBox $wpfNS Name="Id_lb_$id" Margin="0,0,0,0" SelectionMode="Single" Grid.IsSharedSizeScope='True'>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="TitleColumn" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
"@
try
{
$objSection = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $menuXml))
$lstBox = $objSection.FindName("Id_lb_$id")
if($menuSection.Order -gt 0)
{
$order = $menuSection.Order
}
else
{
$order = 90
}
$global:menuObjects += New-Object PSObject -Property @{ ID = $id; MenuInfo = $menuSection; Object = $objSection; MenuItems = @(); MenuListBox = $lstBox; Order = $order }
if($objSection)
{
if($lstBox)
{
$lstBox.Add_SelectionChanged({
if(-not $this.SelectedItem) { return }
$global:menuObjects | ForEach-Object {
if($PSItem.MenuListBox -and $this -ne $PSItem.MenuListBox)
{
# Clear selection in other menu sections
$PSItem.MenuListBox.SelectedItem = $null
}
}
if($this.SelectedItem.ShowForm -ne $false)
{
Clear-Objects
$global:txtFormTitle.Text = $this.SelectedItem.Title
$global:txtFormTitle.Visibility = "Visible"
}
if($this.SelectedItem.Script)
{
Invoke-Command -ScriptBlock $this.SelectedItem.Script
}
Write-Status ""
})
}
}
}
catch { Write-LogError "Failed to add menu section" $_.Exception }
}
function global:Add-MenuItem
{
param($menuItem)
# Get the menu the item should be added to
$objSection = $global:menuObjects | Where { $_.MenuInfo.Id -eq $menuItem.MenuId }
if(-not $objSection)
{
if(($arrMenuInlcude -and $arrMenuInlcude -notcontains $menuItem.MenuId) -or ($arrMenuExlcude -and $arrMenuExlcude -contains $menuItem.MenuId)) { return }
Write-Log "Could not find menu with id $($menuItem.MenuId). Item $($menuItem.Title) not added" 2
return
}
$objSection.MenuItems += $menuItem
}
function global:Invoke-ModuleFunction
{
param($function)
Write-Log "Trigger function $function"
foreach($module in $global:loadedModules)
{
# Get command with ExportedFunctions instead of Get-Command
$cmd = $module.ExportedFunctions[$function]
if($cmd)
{
Write-Log "Trigger $function in $($module.Name)"
Invoke-Command -ScriptBlock $cmd.ScriptBlock
}
else
{
#Write-Log "$function not found in $($module.Name)" 2
}
}
}
function global:Initialize-Menu
{
# Add default menu section
Add-MenuSection (New-Object PSObject -Property @{ Title = "General"; ID="General"; Order = 1000; Sort = $false })
# Add default menu items
Add-MenuItem (New-Object PSObject -Property @{
Title = 'Settings'
MenuID = "General"
Script = [ScriptBlock]{ Show-SettingsForm }
})
Add-MenuItem (New-Object PSObject -Property @{
Title = 'About'
MenuID = "General"
ShowForm = $false
Script = [ScriptBlock]{ Show-AboutDialog }
})
Add-MenuItem (New-Object PSObject -Property @{
Title = 'Reload'
MenuID = "General"
ShowForm = $false
Script = [ScriptBlock]{ Start-Reload }
})
Add-MenuItem (New-Object PSObject -Property @{
Title = 'Exit'
MenuID = "General"
ShowForm = $false
Script = [ScriptBlock]{
if([System.Windows.MessageBox]::Show("Are you sure you want to exit?", "Exit?", "YesNo", "Question") -eq "Yes")
{
$window.Close()
}
$global:menuObjects | ForEach-Object {
# Clear selection in all menu sections - So it can be pressed again
$PSItem.MenuListBox.SelectedItem = $null
}
}
})
# Get all menu items
Invoke-ModuleFunction "Add-ModuleMenuItems"
# Filter and sort menu sections based on order and title
# Add all the menu sections/menuitems to the menu
foreach($menuObj in ($global:menuObjects | Where { $_.MenuItems.Count -gt 0 } | Sort -Property Order))
{
if($menuObj.MenuInfo.Sort -ne $false)
{
$menuObj.MenuItems = ($menuObj.MenuItems | Sort -Property Title)
}
if($menuObj.MenuListBox)
{
$spMenu.Children.Add($menuObj.Object) | Out-Null
$menuObj.MenuListBox.ItemsSource = @($menuObj.MenuItems)
}
}
}
#endregion
#region Console management functions
# https://stackoverflow.com/questions/40617800/opening-powershell-script-and-hide-command-prompt-but-not-the-gui
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
function global:Set-MainTitle
{
if(-not $global:window) { return }
Write-Log "Set main title"
$mainTitle = $title
try
{
if($global:Me.userPrincipalName)
{
$IntuneId = $global:Me.userPrincipalName
$mainTitle += " - IntuneGraph: $($global:Me.userPrincipalName)"
}
}
catch {}
try
{
$ctx = Get-AzContext -ErrorAction SilentlyContinue
if($ctx.Account.Id)
{
$azureADId = $ctx.Account.Id
$mainTitle += " - AzureAD: $($ctx.Account.Id)"
}
}
catch {}
$global:window.Title = $mainTitle
}
function Show-Console
{
$consolePtr = [Console.Window]::GetConsoleWindow()
# Hide = 0,
# ShowNormal = 1,
# ShowMinimized = 2,
# ShowMaximized = 3,
# Maximize = 3,
# ShowNormalNoActivate = 4,
# Show = 5,
# Minimize = 6,
# ShowMinNoActivate = 7,
# ShowNoActivate = 8,
# Restore = 9,
# ShowDefault = 10,
# ForceMinimized = 11
[Console.Window]::ShowWindow($consolePtr, 4)
}
function Hide-Console
{
$consolePtr = [Console.Window]::GetConsoleWindow()
#0 hide
[Console.Window]::ShowWindow($consolePtr, 0)
}
#endregion
#region Module functions
function Import-AllModules
{
foreach($file in (Get-Item -path "$modulesPath\*.psm1"))
{
$module = Import-Module $file -PassThru -Force -ErrorAction SilentlyContinue
if($module)
{
$global:loadedModules += $module
Write-Host "Module $($module.Name) loaded successfully"
}
else
{
Write-Warning "Failed to load module $file"
}
}
}
function Start-Reload
{
if([System.Windows.MessageBox]::Show("Are you sure you want to reload all modules and settings?", "Exit?", "YesNo", "Question") -eq "No")
{
return
}
Write-Status "Reloading modules"
$global:menuObjects = @()
$tmpList = @()
$spMenu.Children.Clear()
foreach($tmpModule in $global:loadedModules)
{
Remove-Module $tmpModule
$module = Import-Module $tmpModule.Path -PassThru -Force -ErrorAction SilentlyContinue
if($module)
{
$tmpList += $module
Write-Host "Module $($module.Name) loaded successfully"
}
else
{
Write-Warning "Failed to load module $file"
}
}
$global:loadedModules = $tmpList
Add-DefaultSettings
Invoke-ModuleFunction "Invoke-InitializeModule"
Initialize-Menu
Write-Status ""
}
#endregion
#####################################################################################################
#
# Main
#
#####################################################################################################
function global:Get-MainWindow
{
$resources = @()
$themes = Join-Path $PSScriptRoot "Themes"
$themFile = Join-Path $themes "Default.xaml"
$resources += $themFile
$styles = Join-Path $themes "Styles.xaml"
$stylesStr = ""
if(Test-Path $styles)
{
try
{
[xml]$styleXml = Get-Content $styles
$stylesStr = $styleXml.FirstChild.InnerXml
}
catch {}
}
[xml]$xaml = @"
<Window
$($global:wpfNS)
Title="$Title"
WindowStartupLocation="CenterScreen"
x:Name="Window">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="$themFile" />
</ResourceDictionary.MergedDictionaries>
$stylesStr
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Name="grdData" Grid.Column="1" Grid.RowSpan="2" Grid.Row="0" Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Name="dgObjects"
SelectionMode="Single"
Grid.Column="1"
Grid.Row="1" />
<TextBlock Name="txtFormTitle" Text="" Background="{DynamicResource TitleBackgroundColor}" Visibility="Collapsed" FontWeight="Bold" Padding="5" Margin="0,0,0,5" />
<StackPanel Grid.Row="2" Name="spSubMenu" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,0" Visibility="Collapsed" />
<Grid Name="grdObject" Grid.Row="1" Grid.RowSpan="2" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White" Margin="0,0,0,0" />
</Grid>
<!-- Left side menu -->
<StackPanel Name="spMenu" Orientation="Vertical" Margin="5,5,5,5" HorizontalAlignment="Stretch" />
<!-- Status that blocks the whole window -->
<Grid Name="grdStatus" Grid.ColumnSpan="2" Grid.RowSpan="3" Background="Black" Opacity="0.5" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Name="txtInfo" Content="" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TitleBackgroundColor}" />
</Grid>
</Grid>
</Window>
"@
$global:window = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
$global:dgObjects = $window.FindName('dgObjects')
$global:grdData = $window.FindName('grdData')
$global:spMenu = $window.FindName('spMenu')
$global:spSubMenu = $window.FindName('spSubMenu')
$global:txtInfo = $window.FindName('txtInfo')
$global:grdStatus = $window.FindName('grdStatus')
$global:grdObject = $window.FindName('grdObject')
$global:txtFormTitle = $window.FindName('txtFormTitle')
$global:dgObjects.Add_AutoGeneratingColumn({
if($_.PropertyName -eq "Object")
{
$_.Cancel = $true
}
})
}
$global:wpfNS = "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'"
Add-Type -AssemblyName PresentationFramework
$global:useDefaultFolderDialog = $false
$global:WindowsAPICodePackLoaded = $false
$global:loadedModules = @()
$global:menuObjects = @()
# Load all modules in the Modules folder
$modulesPath = [IO.Path]::GetDirectoryName($PSCommandPath) + "\Extensions"
if(Test-Path $modulesPath)
{
Import-AllModules
}
else
{
Write-Warning "Modules folder $modulesPath not found. Aborting..." 3
exit 1
}
Add-DefaultSettings
Invoke-ModuleFunction "Invoke-InitializeModule"
#This will load the main window
Get-MainWindow
Initialize-Menu
if($ShowConsoleWindow -ne $true)
{
Hide-Console
}
Set-MainTitle
# Show main window
# Workaround for ISE crash
# https://gist.github.com/altrive/6227237
$async = $global:window.Dispatcher.InvokeAsync({
$global:window.ShowDialog() | Out-Null
})
$async.Wait() | Out-Null