Add headless macOS CLI workflow
This commit is contained in:
@@ -1,6 +1,21 @@
|
||||
#region Console functions
|
||||
|
||||
function Test-IsWindowsPlatform
|
||||
{
|
||||
return ([Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT)
|
||||
}
|
||||
|
||||
function Invoke-AppDoEvents
|
||||
{
|
||||
if("System.Windows.Forms.Application" -as [type])
|
||||
{
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
# https://stackoverflow.com/questions/40617800/opening-powershell-script-and-hide-command-prompt-but-not-the-gui
|
||||
if(Test-IsWindowsPlatform)
|
||||
{
|
||||
Add-Type -Name Window -Namespace Console -MemberDefinition '
|
||||
[DllImport("Kernel32.dll")]
|
||||
public static extern IntPtr GetConsoleWindow();
|
||||
@@ -17,9 +32,11 @@ public static extern bool SetConsoleIcon(IntPtr hIcon);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int SendMessage(int hWnd, uint wMsg, uint wParam, IntPtr lParam);
|
||||
'
|
||||
}
|
||||
|
||||
function Show-Console
|
||||
{
|
||||
if(-not (Test-IsWindowsPlatform)) { return }
|
||||
$consolePtr = [Console.Window]::GetConsoleWindow()
|
||||
|
||||
# Hide = 0,
|
||||
@@ -41,6 +58,7 @@ function Show-Console
|
||||
|
||||
function Hide-Console
|
||||
{
|
||||
if(-not (Test-IsWindowsPlatform)) { return }
|
||||
$consolePtr = [Console.Window]::GetConsoleWindow()
|
||||
#0 hide
|
||||
[Console.Window]::ShowWindow($consolePtr, 0) | Out-Null
|
||||
@@ -94,17 +112,19 @@ function Initialize-CloudAPIManagement
|
||||
)
|
||||
|
||||
$PSModuleAutoloadingPreference = "none"
|
||||
$global:hideUI = ($Silent -eq $true)
|
||||
$global:SilentBatchFile = $SilentBatchFile
|
||||
|
||||
$global:wpfNS = "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'"
|
||||
|
||||
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
|
||||
Add-Type -AssemblyName PresentationFramework
|
||||
if($global:hideUI -ne $true)
|
||||
{
|
||||
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
|
||||
Add-Type -AssemblyName PresentationFramework
|
||||
}
|
||||
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$global:hideUI = ($Silent -eq $true)
|
||||
$global:SilentBatchFile = $SilentBatchFile
|
||||
|
||||
if($tenantId)
|
||||
{
|
||||
Write-Host "Using Tenant Id: $tenantId"
|
||||
@@ -163,7 +183,7 @@ function Initialize-CloudAPIManagement
|
||||
$global:txtSplashTitle.Text = ("Initializing Cloud API PowerShell Management")
|
||||
|
||||
$global:SplashScreen.Show() | Out-Null
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Invoke-AppDoEvents
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -204,15 +224,15 @@ function Initialize-CloudAPIManagement
|
||||
{
|
||||
$global:txtSplashText.Text = "Unblock files"
|
||||
}
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Invoke-AppDoEvents
|
||||
Unblock-AllFiles $PSScriptRoot
|
||||
|
||||
if($global:hideUI -ne $true)
|
||||
{
|
||||
$global:txtSplashText.Text = "Load core module"
|
||||
}
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Import-Module ($PSScriptRoot + "\Core.psm1") -Force -Global
|
||||
Invoke-AppDoEvents
|
||||
Import-Module (Join-Path $PSScriptRoot "Core.psm1") -Force -Global
|
||||
|
||||
Start-CoreApp $View
|
||||
}
|
||||
|
||||
134
Core.psm1
134
Core.psm1
@@ -14,6 +14,14 @@ function Get-ModuleVersion
|
||||
'3.9.6'
|
||||
}
|
||||
|
||||
function Invoke-AppDoEvents
|
||||
{
|
||||
if("System.Windows.Forms.Application" -as [type])
|
||||
{
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-Window
|
||||
{
|
||||
param($xamlFile)
|
||||
@@ -67,7 +75,27 @@ function Start-CoreApp
|
||||
$global:AppRootFolder = $PSScriptRoot
|
||||
|
||||
# Load all modules in the Modules folder
|
||||
$global:modulesPath = [IO.Path]::GetDirectoryName($PSCommandPath) + "\Extensions"
|
||||
$global:modulesPath = Join-Path ([IO.Path]::GetDirectoryName($PSCommandPath)) "Extensions"
|
||||
|
||||
if($global:hideUI -eq $true)
|
||||
{
|
||||
$global:skipModules = @(
|
||||
"Compare.psm1",
|
||||
"Copy.psm1",
|
||||
"Documentation.psm1",
|
||||
"DocumentationCustom.psm1",
|
||||
"DocumentationHTML.psm1",
|
||||
"DocumentationMD.psm1",
|
||||
"DocumentationWord.psm1",
|
||||
"IntuneAssignments.psm1",
|
||||
"IntuneFilterUsage.psm1",
|
||||
"IntuneTools.psm1"
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
$global:skipModules = @()
|
||||
}
|
||||
|
||||
Add-DefaultSettings
|
||||
|
||||
@@ -122,7 +150,7 @@ function Start-CoreApp
|
||||
$global:MainAppStarted = $false
|
||||
|
||||
Set-SplashWindowText "Initialize views"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Invoke-AppDoEvents
|
||||
|
||||
Invoke-ModuleFunction "Invoke-InitializeModule"
|
||||
|
||||
@@ -176,8 +204,6 @@ function Start-CoreApp
|
||||
Write-Log "SilentBatchFile $($global:SilentBatchFile) not found" 3
|
||||
return
|
||||
}
|
||||
Invoke-ModuleFunction "Invoke-ShowMainWindow"
|
||||
|
||||
Invoke-ModuleFunction "Invoke-InitSilentBatchJob"
|
||||
|
||||
Start-RunSilentBatchJob
|
||||
@@ -199,13 +225,13 @@ function Start-RunSilentBatchJob
|
||||
|
||||
function Import-AllModules
|
||||
{
|
||||
foreach($file in (Get-Item -path "$($global:modulesPath)\*.psm1"))
|
||||
foreach($file in (Get-Item -path (Join-Path $global:modulesPath "*.psm1")))
|
||||
{
|
||||
$fileName = [IO.Path]::GetFileName($file)
|
||||
if($skipModules -contains $fileName) { Write-Warning "Module $fileName excluded"; continue; }
|
||||
|
||||
Set-SplashWindowText "Import module $fileName"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Invoke-AppDoEvents
|
||||
|
||||
$module = Import-Module $file -PassThru -Force -Global -ErrorAction SilentlyContinue
|
||||
if($module)
|
||||
@@ -422,7 +448,14 @@ function Set-XamlProperty
|
||||
{
|
||||
if($obj)
|
||||
{
|
||||
$obj."$propertyName" = $value
|
||||
if(-not ($obj.PSObject.Properties.Name -contains $propertyName))
|
||||
{
|
||||
$obj | Add-Member -MemberType NoteProperty -Name $propertyName -Value $value -Force
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj."$propertyName" = $value
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -571,15 +604,15 @@ function Set-BatchProperties
|
||||
|
||||
try
|
||||
{
|
||||
if($obj -is [System.Windows.Controls.CheckBox])
|
||||
if($obj.PSObject.Properties.Name -contains "IsChecked")
|
||||
{
|
||||
$obj.IsChecked = $prop.Value -eq $true
|
||||
}
|
||||
elseif($obj -is [System.Windows.Controls.TextBox])
|
||||
elseif($obj.PSObject.Properties.Name -contains "Text")
|
||||
{
|
||||
$obj.Text = $prop.Value
|
||||
}
|
||||
elseif($obj -is [System.Windows.Controls.ComboBox])
|
||||
elseif($obj.PSObject.Properties.Name -contains "SelectedValue")
|
||||
{
|
||||
$obj.SelectedValue = $prop.Value
|
||||
}
|
||||
@@ -601,6 +634,60 @@ function Set-BatchProperties
|
||||
}
|
||||
#endregion
|
||||
|
||||
function New-HeadlessControl
|
||||
{
|
||||
param(
|
||||
[string]$Name,
|
||||
[ValidateSet("TextBox","CheckBox","ComboBox","Label","DataGrid")]
|
||||
[string]$Type = "TextBox"
|
||||
)
|
||||
|
||||
$control = [PSCustomObject]@{
|
||||
Name = $Name
|
||||
Focusable = $true
|
||||
Visibility = "Visible"
|
||||
IsEnabled = $true
|
||||
Text = ""
|
||||
IsChecked = $false
|
||||
SelectedValue = $null
|
||||
ItemsSource = @()
|
||||
Content = ""
|
||||
}
|
||||
|
||||
if($Type -eq "DataGrid")
|
||||
{
|
||||
$control.Focusable = $false
|
||||
}
|
||||
|
||||
$control
|
||||
}
|
||||
|
||||
function New-HeadlessForm
|
||||
{
|
||||
param($Controls)
|
||||
|
||||
$form = [PSCustomObject]@{
|
||||
Controls = @{}
|
||||
}
|
||||
|
||||
foreach($control in $Controls)
|
||||
{
|
||||
$form.Controls[$control.Name] = $control
|
||||
}
|
||||
|
||||
$form | Add-Member -MemberType ScriptMethod -Name FindName -Value {
|
||||
param($name)
|
||||
$this.Controls[$name]
|
||||
}
|
||||
|
||||
$form | Add-Member -MemberType ScriptMethod -Name RegisterName -Value {
|
||||
param($name, $control)
|
||||
$this.Controls[$name] = $control
|
||||
}
|
||||
|
||||
$form
|
||||
}
|
||||
|
||||
#region Dialogs
|
||||
|
||||
function Show-AboutDialog
|
||||
@@ -1204,6 +1291,21 @@ function Expand-FileName
|
||||
|
||||
#endregion
|
||||
|
||||
function Get-CloudApiDataFolder
|
||||
{
|
||||
if($env:LOCALAPPDATA)
|
||||
{
|
||||
return (Join-Path $env:LOCALAPPDATA "CloudAPIPowerShellManagement")
|
||||
}
|
||||
|
||||
if(Test-IsWindowsPlatform)
|
||||
{
|
||||
return (Join-Path $env:USERPROFILE "AppData/Local/CloudAPIPowerShellManagement")
|
||||
}
|
||||
|
||||
return (Join-Path $HOME "Library/Application Support/CloudAPIPowerShellManagement")
|
||||
}
|
||||
|
||||
#region Save/Read Settings functions
|
||||
########################################################################
|
||||
#
|
||||
@@ -1231,7 +1333,7 @@ function Initialize-JsonSettings
|
||||
{
|
||||
if(-not $global:JSonSettingFile)
|
||||
{
|
||||
$global:JSonSettingFile = "$($env:LOCALAPPDATA)\CloudAPIPowerShellManagement\Settings.json"
|
||||
$global:JSonSettingFile = Join-Path (Get-CloudApiDataFolder) "Settings.json"
|
||||
$fi = [IO.FileInfo]$global:JSonSettingFile
|
||||
if($fi.Exists -eq $false)
|
||||
{
|
||||
@@ -1548,7 +1650,7 @@ function Add-RegKeyToSettings
|
||||
try
|
||||
{
|
||||
$keyObj = Get-Item -Path $regKey -ErrorAction SilentlyContinue
|
||||
foreach($keyValue in ($keyObj.GetValueNames() | Sort))
|
||||
foreach($keyValue in ($keyObj.GetValueNames() | Sort-Object))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1560,7 +1662,7 @@ function Add-RegKeyToSettings
|
||||
}
|
||||
}
|
||||
|
||||
foreach($subKey in ($keyObj.GetSubKeyNames() | Sort))
|
||||
foreach($subKey in ($keyObj.GetSubKeyNames() | Sort-Object))
|
||||
{
|
||||
|
||||
$settingObjSub = [ordered]@{}
|
||||
@@ -1967,7 +2069,7 @@ function Add-DefaultSettings
|
||||
Value = ""
|
||||
}
|
||||
|
||||
foreach($color in ([System.Drawing.Color].GetProperties() | Where { $_.PropertyType -eq [System.Drawing.Color] } | Sort -Property Name | Select Name).Name)
|
||||
foreach($color in ([System.Drawing.Color].GetProperties() | Where { $_.PropertyType -eq [System.Drawing.Color] } | Sort-Object -Property Name | Select Name).Name)
|
||||
{
|
||||
$script:lstColors += [PSCustomObject]@{
|
||||
Name = $color
|
||||
@@ -2286,7 +2388,7 @@ function Add-ViewItem
|
||||
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")))
|
||||
if($global:hideUI -ne $true -and ($viewItem.Icon -or [IO.File]::Exists(($global:AppRootFolder + "\Xaml\Icons\$($viewItem.Id).xaml"))))
|
||||
{
|
||||
$ctrl = Get-XamlObject ($global:AppRootFolder + "\Xaml\Icons\$((?? $viewItem.Icon $viewItem.Id)).xaml")
|
||||
$viewItem | Add-Member -NotePropertyName "IconImage" -NotePropertyValue $ctrl
|
||||
@@ -2926,4 +3028,4 @@ function Get-GUIDs
|
||||
|
||||
New-Alias -Name ?? -value Invoke-Coalesce
|
||||
New-Alias -Name ?: -value Invoke-IfTrue
|
||||
Export-ModuleMember -alias * -function *
|
||||
Export-ModuleMember -alias * -function *
|
||||
|
||||
@@ -105,9 +105,12 @@ function Invoke-InitializeModule
|
||||
Write-Log "Microsoft Intune PowerShell is being decomissioned. Please change to a supported app eg Microsoft Graph or a custom app!" 2
|
||||
}
|
||||
|
||||
$viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerPanel.xaml") -AddVariables
|
||||
|
||||
Set-EMViewPanel $viewPanel
|
||||
$viewPanel = $null
|
||||
if($global:hideUI -ne $true)
|
||||
{
|
||||
$viewPanel = Get-XamlObject (Join-Path $global:AppRootFolder "Xaml/EndpointManagerPanel.xaml") -AddVariables
|
||||
Set-EMViewPanel $viewPanel
|
||||
}
|
||||
|
||||
#Add menu group and items
|
||||
$global:EMViewObject = (New-Object PSObject -Property @{
|
||||
@@ -4417,4 +4420,4 @@ function Start-PreImportCommandAuthenticationContext
|
||||
#endregion
|
||||
|
||||
|
||||
Export-ModuleMember -alias * -function *
|
||||
Export-ModuleMember -alias * -function *
|
||||
|
||||
@@ -138,8 +138,8 @@ function Invoke-InitializeModule
|
||||
}) "MSAL"
|
||||
|
||||
$script:MSALUseWAM = Get-SettingValue "UseWAM"
|
||||
if($script:MSALUseWAM -and $PSVersionTable.PSVersion.Major -lt 7) {
|
||||
Write-Log "WAM is only supported in PowerShell 7 and later. Disabling WAM" 2
|
||||
if($script:MSALUseWAM -and ($PSVersionTable.PSVersion.Major -lt 7 -or -not (Test-IsWindowsPlatform))) {
|
||||
Write-Log "WAM is only supported on Windows with PowerShell 7 and later. Disabling WAM" 2
|
||||
$script:MSALUseWAM = $false
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ function Get-MSALUserInfo
|
||||
### Only get user info from home tenant
|
||||
$global:Me = $tmpMe
|
||||
Write-Log "Get profile picture"
|
||||
$global:profilePhoto = "$($env:LOCALAPPDATA)\CloudAPIPowerShellManagement\$($global:Me.Id).jpeg"
|
||||
$global:profilePhoto = Join-Path (Get-CloudApiDataFolder) "$($global:Me.Id).jpeg"
|
||||
MSGraph\Invoke-GraphRequest "me/photos/48x48/`$value" -OutFile $global:profilePhoto -SkipAuthentication -NoError | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -517,17 +517,17 @@ function Add-MSALPrereq
|
||||
|
||||
$DLLFiles = @()
|
||||
if($PSVersionTable.PSVersion.Major -ge 7) {
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\Bin\Microsoft.IdentityModel.Abstractions.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/Microsoft.IdentityModel.Abstractions.dll")
|
||||
}
|
||||
else {
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\Bin\6.35.0\Microsoft.IdentityModel.Abstractions.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/6.35.0/Microsoft.IdentityModel.Abstractions.dll")
|
||||
}
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\Bin\Microsoft.Identity.Client.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/Microsoft.Identity.Client.dll")
|
||||
if($script:MSALUseWAM) {
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\BIN\$MSALDLLPath\Microsoft.Identity.Client.Extensions.Msal.dll")
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\BIN\$MSALDLLPath\Microsoft.Identity.Client.Broker.dll")
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\BIN\$MSALDLLPath\Microsoft.Identity.Client.Desktop.dll")
|
||||
$DLLFiles += [IO.FileInfo]($ScriptRoot + "\BIN\$MSALDLLPath\Microsoft.Identity.Client.NativeInterop.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/$MSALDLLPath/Microsoft.Identity.Client.Extensions.Msal.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/$MSALDLLPath/Microsoft.Identity.Client.Broker.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/$MSALDLLPath/Microsoft.Identity.Client.Desktop.dll")
|
||||
$DLLFiles += [IO.FileInfo](Join-Path $ScriptRoot "Bin/$MSALDLLPath/Microsoft.Identity.Client.NativeInterop.dll")
|
||||
}
|
||||
|
||||
$DLLFiles | ForEach-Object {
|
||||
@@ -570,7 +570,7 @@ function Add-MSALPrereq
|
||||
|
||||
try
|
||||
{
|
||||
Add-Type -Path ($ScriptRoot + "\CS\TokenCacheHelperEx.cs") -ReferencedAssemblies $RequiredAssemblies -IgnoreWarnings
|
||||
Add-Type -Path (Join-Path $ScriptRoot "CS/TokenCacheHelperEx.cs") -ReferencedAssemblies $RequiredAssemblies -IgnoreWarnings
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -809,7 +809,7 @@ function Get-MsalAuthenticationToken
|
||||
{
|
||||
# Login hung on rare occations
|
||||
# Workaround: Added DoEvents
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Invoke-AppDoEvents
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
@@ -954,7 +954,7 @@ function Get-MSALApp
|
||||
|
||||
if($global:SkipTokenCacheHelperEx -ne $true -and (Get-SettingValue "CacheMSALToken"))
|
||||
{
|
||||
[TokenCacheHelperEx]::EnableSerialization($msalApp.UserTokenCache, "%LOCALAPPDATA%\CloudAPIPowerShellManagement\msalcahce.bin3")
|
||||
[TokenCacheHelperEx]::EnableSerialization($msalApp.UserTokenCache, (Join-Path (Get-CloudApiDataFolder) "msalcahce.bin3"))
|
||||
}
|
||||
$script:MSALAllApps += $msalApp
|
||||
}
|
||||
@@ -1350,7 +1350,7 @@ function Connect-MSALUser
|
||||
|
||||
if((Get-SettingValue "CacheMSALToken"))
|
||||
{
|
||||
[TokenCacheHelperEx]::EnableSerialization($app.UserTokenCache, "%LOCALAPPDATA%\CloudAPIPowerShellManagement\msalcahce.bin3")
|
||||
[TokenCacheHelperEx]::EnableSerialization($app.UserTokenCache, (Join-Path (Get-CloudApiDataFolder) "msalcahce.bin3"))
|
||||
}
|
||||
|
||||
### Silent login
|
||||
@@ -2377,4 +2377,4 @@ function Show-MSALDecodedToken {
|
||||
Show-ModalForm $title $dg
|
||||
}
|
||||
|
||||
Export-ModuleMember -alias * -function *
|
||||
Export-ModuleMember -alias * -function *
|
||||
|
||||
@@ -782,7 +782,7 @@ function Add-GraphObjectProperties
|
||||
|
||||
if($objects.Count -gt 0 -and $SortProperty -and ($objects[0] | GM -MemberType NoteProperty -Name $SortProperty))
|
||||
{
|
||||
$objects = $objects | sort -Property $SortProperty
|
||||
$objects = $objects | Sort-Object -Property $SortProperty
|
||||
}
|
||||
|
||||
$objects
|
||||
@@ -1400,12 +1400,77 @@ function Invoke-SilentBatchJob
|
||||
}
|
||||
}
|
||||
|
||||
function New-GraphSilentBatchForm
|
||||
{
|
||||
param($Controls)
|
||||
|
||||
$form = New-HeadlessForm $Controls
|
||||
|
||||
foreach($control in $Controls)
|
||||
{
|
||||
New-Variable -Name $control.Name -Value $control -Force -Scope Global
|
||||
}
|
||||
|
||||
$form
|
||||
}
|
||||
|
||||
function New-GraphSilentExportForm
|
||||
{
|
||||
$controls = @(
|
||||
(New-HeadlessControl -Name "txtExportPath" -Type "TextBox"),
|
||||
(New-HeadlessControl -Name "txtExportNameFilter" -Type "TextBox"),
|
||||
(New-HeadlessControl -Name "chkAddObjectType" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "chkExportAssignments" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "chkAddCompanyName" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "dgObjectsToExport" -Type "DataGrid")
|
||||
)
|
||||
|
||||
$form = New-GraphSilentBatchForm $controls
|
||||
Set-XamlProperty $form "txtExportPath" "Text" (?? (Get-Setting "" "LastUsedRoot") (Get-SettingValue "RootFolder"))
|
||||
Set-XamlProperty $form "chkAddObjectType" "IsChecked" $true
|
||||
Set-XamlProperty $form "chkExportAssignments" "IsChecked" (Get-SettingValue "ExportAssignments")
|
||||
Set-XamlProperty $form "chkAddCompanyName" "IsChecked" (Get-SettingValue "AddCompanyName")
|
||||
|
||||
$form
|
||||
}
|
||||
|
||||
function New-GraphSilentImportForm
|
||||
{
|
||||
$path = Get-Setting "" "LastUsedFullPath"
|
||||
if($path)
|
||||
{
|
||||
$path = [IO.Directory]::GetParent($path).FullName
|
||||
}
|
||||
|
||||
$controls = @(
|
||||
(New-HeadlessControl -Name "txtImportPath" -Type "TextBox"),
|
||||
(New-HeadlessControl -Name "txtImportNameFilter" -Type "TextBox"),
|
||||
(New-HeadlessControl -Name "lblMigrationTableInfo" -Type "Label"),
|
||||
(New-HeadlessControl -Name "chkAddObjectType" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "chkImportScopes" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "chkImportAssignments" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "chkReplaceDependencyIDs" -Type "CheckBox"),
|
||||
(New-HeadlessControl -Name "cbImportType" -Type "ComboBox"),
|
||||
(New-HeadlessControl -Name "dgObjectsToImport" -Type "DataGrid")
|
||||
)
|
||||
|
||||
$form = New-GraphSilentBatchForm $controls
|
||||
Set-XamlProperty $form "txtImportPath" "Text" (?? $path (Get-SettingValue "RootFolder"))
|
||||
Set-XamlProperty $form "chkAddObjectType" "IsChecked" $true
|
||||
Set-XamlProperty $form "chkImportAssignments" "IsChecked" (Get-SettingValue "ImportAssignments")
|
||||
Set-XamlProperty $form "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags")
|
||||
Set-XamlProperty $form "chkReplaceDependencyIDs" "IsChecked" $true
|
||||
Set-XamlProperty $form "cbImportType" "ItemsSource" $script:lstImportTypes
|
||||
Set-XamlProperty $form "cbImportType" "SelectedValue" (Get-SettingValue "ImportType" "alwaysImport")
|
||||
|
||||
$form
|
||||
}
|
||||
|
||||
function Start-GraphSilentBulkExport
|
||||
{
|
||||
param($settingsObj)
|
||||
|
||||
$script:exportForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkExportForm.xaml") -AddVariables
|
||||
if(-not $script:exportForm) { return }
|
||||
$script:exportForm = New-GraphSilentExportForm
|
||||
|
||||
$script:exportObjects = Get-GraphBatchObjectTypes $settingsObj.BulkExport
|
||||
|
||||
@@ -1414,8 +1479,6 @@ function Start-GraphSilentBulkExport
|
||||
if(-not $viewObj.Title) { continue }
|
||||
|
||||
if($viewObj.ObjectType.ShowButtons -is [Object[]] -and $viewObj.ObjectType.ShowButtons -notcontains "Export") { continue }
|
||||
|
||||
Add-GraphExportExtensions $script:exportForm 0 $viewObj.ObjectType
|
||||
}
|
||||
|
||||
Set-BatchProperties $settingsObj.BulkExport $script:exportForm
|
||||
@@ -1449,8 +1512,7 @@ function Start-GraphSilentBulkImport
|
||||
{
|
||||
param($settingsObj)
|
||||
|
||||
$script:importForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\BulkImportForm.xaml") -AddVariables
|
||||
if(-not $script:importForm) { return }
|
||||
$script:importForm = New-GraphSilentImportForm
|
||||
|
||||
# Get all objects but not selected
|
||||
# This will allow dependencies
|
||||
@@ -1479,11 +1541,10 @@ function Start-GraphSilentBulkImport
|
||||
if(-not $viewObj.Title) { continue }
|
||||
|
||||
if($viewObj.ObjectType.ShowButtons -is [Object[]] -and $viewObj.ObjectType.ShowButtons -notcontains "Import") { continue }
|
||||
|
||||
Add-GraphImportExtensions $script:importForm 0 $viewObj.ObjectType
|
||||
}
|
||||
|
||||
Set-BatchProperties $settingsObj.BulkImport $script:importForm
|
||||
Set-XamlProperty $script:importForm "lblMigrationTableInfo" "Content" (Get-MigrationTableInfo)
|
||||
|
||||
$global:dgObjectsToImport.ItemsSource = @($script:importObjects)
|
||||
|
||||
@@ -2358,7 +2419,7 @@ function Get-GraphFileObjects
|
||||
}
|
||||
|
||||
$fileArr = @()
|
||||
foreach($file in (Get-Item -path "$path\*.json" @params))
|
||||
foreach($file in (Get-Item -path (Join-Path $path "*.json") @params))
|
||||
{
|
||||
if($ObjectType.LoadObject)
|
||||
{
|
||||
@@ -2864,7 +2925,7 @@ function Add-GroupMigrationObject
|
||||
# Export group info to json file for possible import
|
||||
$grouspPath = Join-Path $path "Groups"
|
||||
if(-not (Test-Path $grouspPath)) { mkdir -Path $grouspPath -Force -ErrorAction SilentlyContinue | Out-Null }
|
||||
$fileName = "$grouspPath\$((Remove-InvalidFileNameChars $groupObj.displayName)).json"
|
||||
$fileName = Join-Path $grouspPath "$((Remove-InvalidFileNameChars $groupObj.displayName)).json"
|
||||
Save-GraphObjectToFile $groupObj $fileName
|
||||
}
|
||||
}
|
||||
@@ -2907,7 +2968,7 @@ function Add-GraphMigrationObject
|
||||
# Export group info to json file for possible import
|
||||
$grouspPath = Join-Path $path "Groups"
|
||||
if(-not (Test-Path $grouspPath)) { mkdir -Path $grouspPath -Force -ErrorAction SilentlyContinue | Out-Null }
|
||||
$fileName = "$grouspPath\$((Remove-InvalidFileNameChars $graphObj.displayName)).json"
|
||||
$fileName = Join-Path $grouspPath "$((Remove-InvalidFileNameChars $graphObj.displayName)).json"
|
||||
Save-GraphObjectToFile $graphObj $fileName
|
||||
}
|
||||
}
|
||||
@@ -3050,7 +3111,7 @@ function Get-GraphMigrationObjectsFromFile
|
||||
if($global:GraphMigrationTable)
|
||||
{
|
||||
$fi = [IO.FileInfo]$global:GraphMigrationTable
|
||||
$groupFi = [IO.FileInfo]($fi.DirectoryName + "\Groups\$((Remove-InvalidFileNameChars $migTableGroupName)).json")
|
||||
$groupFi = [IO.FileInfo](Join-Path (Join-Path $fi.DirectoryName "Groups") "$((Remove-InvalidFileNameChars $migTableGroupName)).json")
|
||||
}
|
||||
|
||||
if($groupFi.Exists -eq $true)
|
||||
@@ -3197,13 +3258,13 @@ function Add-GraphDependencyObjects
|
||||
continue
|
||||
}
|
||||
|
||||
if([IO.Directory]::Exists(($importPath + "\" + $dep)))
|
||||
if([IO.Directory]::Exists((Join-Path $importPath $dep)))
|
||||
{
|
||||
$path = ($importPath + "\" + $dep)
|
||||
$path = (Join-Path $importPath $dep)
|
||||
}
|
||||
elseif([IO.Directory]::Exists(($parentPath + "\" + $dep)))
|
||||
elseif([IO.Directory]::Exists((Join-Path $parentPath $dep)))
|
||||
{
|
||||
$path = ($parentPath + "\" + $dep)
|
||||
$path = (Join-Path $parentPath $dep)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3674,7 +3735,7 @@ function Invoke-GraphBatchRequest
|
||||
$retryAfter = 0
|
||||
$tmpResults = Invoke-GraphRequest -Url "`$batch" -Body $json -Method "POST"
|
||||
|
||||
foreach($batchResult in ($tmpResults.responses | Sort -Property Id))
|
||||
foreach($batchResult in ($tmpResults.responses | Sort-Object -Property Id))
|
||||
{
|
||||
if($batchResult.Status -ge 300 -or -not $batchResult.body)
|
||||
{
|
||||
@@ -4151,7 +4212,7 @@ function Show-GraphObjectInfo
|
||||
if($prop.Name.Contains('@') -or $prop.Name.Contains('#')) { continue }
|
||||
$objProps += ([PSCustomObject]@{ Name=$prop.Name;Value=$prop })
|
||||
}
|
||||
$objProps = $objProps | sort -Property Name
|
||||
$objProps = $objProps | Sort-Object -Property Name
|
||||
Set-XamlProperty $script:detailsForm "lstObjectProperties" "ItemsSource" $objProps
|
||||
|
||||
Add-XamlEvent $script:detailsForm "btnObjectColumnsReset" "Add_Click" -scriptBlock ([scriptblock]{
|
||||
@@ -4633,4 +4694,4 @@ function Confirm-GraphMatchFilter
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
17
Headless/IntuneManagement.Headless.psd1
Normal file
17
Headless/IntuneManagement.Headless.psd1
Normal file
@@ -0,0 +1,17 @@
|
||||
@{
|
||||
RootModule = 'IntuneManagement.Headless.psm1'
|
||||
ModuleVersion = '0.1.0'
|
||||
GUID = 'b5b4183d-8d6b-4b31-bbde-f2f0f0a0739d'
|
||||
Author = 'OpenAI Codex'
|
||||
Copyright = '(c) OpenAI. Adapter module for headless Intune policy migration.'
|
||||
Description = 'Headless export/import wrapper for IntuneManagement.'
|
||||
FunctionsToExport = @(
|
||||
'Get-DefaultIntunePolicyObjectTypes',
|
||||
'Export-IntunePolicies',
|
||||
'Import-IntunePolicies',
|
||||
'Invoke-IntunePolicyAction'
|
||||
)
|
||||
AliasesToExport = @()
|
||||
VariablesToExport = @()
|
||||
CmdletsToExport = @()
|
||||
}
|
||||
311
Headless/IntuneManagement.Headless.psm1
Normal file
311
Headless/IntuneManagement.Headless.psm1
Normal file
@@ -0,0 +1,311 @@
|
||||
function Get-DefaultIntunePolicyObjectTypes
|
||||
{
|
||||
@(
|
||||
"DeviceConfiguration",
|
||||
"SettingsCatalog",
|
||||
"AdministrativeTemplates",
|
||||
"CompliancePolicies",
|
||||
"EndpointSecurity",
|
||||
"PolicySets"
|
||||
)
|
||||
}
|
||||
|
||||
function Get-IntuneManagementProjectRoot
|
||||
{
|
||||
Split-Path -Parent $PSScriptRoot
|
||||
}
|
||||
|
||||
function Resolve-HeadlessSettingsPath
|
||||
{
|
||||
param([string]$SettingsFile)
|
||||
|
||||
if($SettingsFile)
|
||||
{
|
||||
return $SettingsFile
|
||||
}
|
||||
|
||||
Join-Path ([IO.Path]::GetTempPath()) "IntuneManagement.Settings.json"
|
||||
}
|
||||
|
||||
function New-TemporaryBatchFile
|
||||
{
|
||||
param([string]$Prefix)
|
||||
|
||||
Join-Path ([IO.Path]::GetTempPath()) ("IntuneManagement.{0}.{1}.json" -f $Prefix, [guid]::NewGuid().ToString())
|
||||
}
|
||||
|
||||
function Test-AuthParameters
|
||||
{
|
||||
param(
|
||||
[string]$Secret,
|
||||
[string]$Certificate
|
||||
)
|
||||
|
||||
if((-not $Secret) -and (-not $Certificate))
|
||||
{
|
||||
throw "Specify -Secret or -Certificate."
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-IntuneHeadlessBatch
|
||||
{
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$BatchConfig,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile
|
||||
)
|
||||
|
||||
Test-AuthParameters -Secret $Secret -Certificate $Certificate
|
||||
|
||||
$projectRoot = Get-IntuneManagementProjectRoot
|
||||
$startScript = Join-Path $projectRoot "Start-IntuneManagement.ps1"
|
||||
|
||||
if(-not (Test-Path $startScript))
|
||||
{
|
||||
throw "Could not find Start-IntuneManagement.ps1 in $projectRoot"
|
||||
}
|
||||
|
||||
$settingsPath = Resolve-HeadlessSettingsPath $SettingsFile
|
||||
|
||||
$deleteBatchFile = $false
|
||||
if(-not $BatchFile)
|
||||
{
|
||||
$BatchFile = New-TemporaryBatchFile "Batch"
|
||||
$deleteBatchFile = $true
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$BatchConfig | ConvertTo-Json -Depth 20 | Out-File -LiteralPath $BatchFile -Encoding utf8 -Force
|
||||
|
||||
$invokeParams = @{
|
||||
Silent = $true
|
||||
JSonSettings = $true
|
||||
JSonFile = $settingsPath
|
||||
TenantId = $TenantId
|
||||
AppId = $AppId
|
||||
SilentBatchFile = $BatchFile
|
||||
}
|
||||
|
||||
if($Secret)
|
||||
{
|
||||
$invokeParams.Secret = $Secret
|
||||
}
|
||||
else
|
||||
{
|
||||
$invokeParams.Certificate = $Certificate
|
||||
}
|
||||
|
||||
& $startScript @invokeParams
|
||||
}
|
||||
finally
|
||||
{
|
||||
if($deleteBatchFile -and (Test-Path $BatchFile))
|
||||
{
|
||||
Remove-Item -LiteralPath $BatchFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Export-IntunePolicies
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExportPath,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$AddCompanyName
|
||||
)
|
||||
|
||||
$batchConfig = [PSCustomObject]@{
|
||||
BulkExport = @(
|
||||
[PSCustomObject]@{ Name = "txtExportPath"; Value = $ExportPath },
|
||||
[PSCustomObject]@{ Name = "txtExportNameFilter"; Value = $NameFilter },
|
||||
[PSCustomObject]@{ Name = "chkAddObjectType"; Value = $true },
|
||||
[PSCustomObject]@{ Name = "chkExportAssignments"; Value = $IncludeAssignments.IsPresent },
|
||||
[PSCustomObject]@{ Name = "chkAddCompanyName"; Value = $AddCompanyName.IsPresent },
|
||||
[PSCustomObject]@{ Name = "ObjectTypes"; Type = "Custom"; ObjectTypes = @($ObjectTypes) }
|
||||
)
|
||||
}
|
||||
|
||||
Invoke-IntuneHeadlessBatch `
|
||||
-TenantId $TenantId `
|
||||
-AppId $AppId `
|
||||
-Secret $Secret `
|
||||
-Certificate $Certificate `
|
||||
-BatchConfig $batchConfig `
|
||||
-SettingsFile $SettingsFile `
|
||||
-BatchFile $BatchFile
|
||||
}
|
||||
|
||||
function Import-IntunePolicies
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ImportPath,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
|
||||
[string]$ImportType = "alwaysImport",
|
||||
|
||||
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$IncludeScopeTags,
|
||||
|
||||
[switch]$ReplaceDependencyIds
|
||||
)
|
||||
|
||||
$batchConfig = [PSCustomObject]@{
|
||||
BulkImport = @(
|
||||
[PSCustomObject]@{ Name = "txtImportPath"; Value = $ImportPath },
|
||||
[PSCustomObject]@{ Name = "txtImportNameFilter"; Value = $NameFilter },
|
||||
[PSCustomObject]@{ Name = "chkAddObjectType"; Value = $true },
|
||||
[PSCustomObject]@{ Name = "chkImportScopes"; Value = $IncludeScopeTags.IsPresent },
|
||||
[PSCustomObject]@{ Name = "chkImportAssignments"; Value = $IncludeAssignments.IsPresent },
|
||||
[PSCustomObject]@{ Name = "chkReplaceDependencyIDs"; Value = $ReplaceDependencyIds.IsPresent },
|
||||
[PSCustomObject]@{ Name = "cbImportType"; Value = $ImportType },
|
||||
[PSCustomObject]@{ Name = "ObjectTypes"; Type = "Custom"; ObjectTypes = @($ObjectTypes) }
|
||||
)
|
||||
}
|
||||
|
||||
Invoke-IntuneHeadlessBatch `
|
||||
-TenantId $TenantId `
|
||||
-AppId $AppId `
|
||||
-Secret $Secret `
|
||||
-Certificate $Certificate `
|
||||
-BatchConfig $batchConfig `
|
||||
-SettingsFile $SettingsFile `
|
||||
-BatchFile $BatchFile
|
||||
}
|
||||
|
||||
function Invoke-IntunePolicyAction
|
||||
{
|
||||
[CmdletBinding(DefaultParameterSetName = 'Export')]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet("Export","Import")]
|
||||
[string]$Action,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
|
||||
|
||||
[string]$ExportPath,
|
||||
|
||||
[string]$ImportPath,
|
||||
|
||||
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
|
||||
[string]$ImportType = "alwaysImport",
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$AddCompanyName,
|
||||
|
||||
[switch]$IncludeScopeTags,
|
||||
|
||||
[switch]$ReplaceDependencyIds
|
||||
)
|
||||
|
||||
switch($Action)
|
||||
{
|
||||
"Export"
|
||||
{
|
||||
if(-not $ExportPath) { throw "Export requires -ExportPath." }
|
||||
Export-IntunePolicies `
|
||||
-TenantId $TenantId `
|
||||
-AppId $AppId `
|
||||
-Secret $Secret `
|
||||
-Certificate $Certificate `
|
||||
-ExportPath $ExportPath `
|
||||
-SettingsFile $SettingsFile `
|
||||
-BatchFile $BatchFile `
|
||||
-NameFilter $NameFilter `
|
||||
-ObjectTypes $ObjectTypes `
|
||||
-IncludeAssignments:$IncludeAssignments `
|
||||
-AddCompanyName:$AddCompanyName
|
||||
}
|
||||
"Import"
|
||||
{
|
||||
if(-not $ImportPath) { throw "Import requires -ImportPath." }
|
||||
Import-IntunePolicies `
|
||||
-TenantId $TenantId `
|
||||
-AppId $AppId `
|
||||
-Secret $Secret `
|
||||
-Certificate $Certificate `
|
||||
-ImportPath $ImportPath `
|
||||
-SettingsFile $SettingsFile `
|
||||
-BatchFile $BatchFile `
|
||||
-NameFilter $NameFilter `
|
||||
-ImportType $ImportType `
|
||||
-ObjectTypes $ObjectTypes `
|
||||
-IncludeAssignments:$IncludeAssignments `
|
||||
-IncludeScopeTags:$IncludeScopeTags `
|
||||
-ReplaceDependencyIds:$ReplaceDependencyIds
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Headless/README.md
Normal file
40
Headless/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# IntuneManagement Headless
|
||||
|
||||
This is the CLI-first surface for a cross-platform fork of the original IntuneManagement project.
|
||||
|
||||
The original project is still a Windows WPF application. This layer treats that codebase as an implementation backend and exposes a smaller product surface focused on:
|
||||
|
||||
* app-only authentication
|
||||
* headless export/import
|
||||
* macOS/Linux/Windows execution with `pwsh`
|
||||
* automation and CI usage
|
||||
|
||||
## Entry points
|
||||
|
||||
* [Start-HeadlessIntune.ps1](/Users/avedelphina/Local/IntuneManagement/Start-HeadlessIntune.ps1)
|
||||
* [Scripts/Export-Policies.ps1](/Users/avedelphina/Local/IntuneManagement/Scripts/Export-Policies.ps1)
|
||||
* [Scripts/Import-Policies.ps1](/Users/avedelphina/Local/IntuneManagement/Scripts/Import-Policies.ps1)
|
||||
* [Headless/IntuneManagement.Headless.psd1](/Users/avedelphina/Local/IntuneManagement/Headless/IntuneManagement.Headless.psd1)
|
||||
|
||||
## Default policy scope
|
||||
|
||||
The default object types are:
|
||||
|
||||
* `DeviceConfiguration`
|
||||
* `SettingsCatalog`
|
||||
* `AdministrativeTemplates`
|
||||
* `CompliancePolicies`
|
||||
* `EndpointSecurity`
|
||||
* `PolicySets`
|
||||
|
||||
## Example
|
||||
|
||||
```powershell
|
||||
pwsh ./Start-HeadlessIntune.ps1 `
|
||||
-Action Export `
|
||||
-TenantId "<source-tenant-id>" `
|
||||
-AppId "<app-id>" `
|
||||
-Secret "<client-secret>" `
|
||||
-ExportPath "/tmp/intune-export" `
|
||||
-IncludeAssignments
|
||||
```
|
||||
136
README.md
136
README.md
@@ -1,4 +1,14 @@
|
||||
# IntuneManagement with PowerShell and WPF UI
|
||||
# IntuneManagement
|
||||
|
||||
This repository now contains two usable surfaces:
|
||||
|
||||
* the original Windows WPF application
|
||||
* a newer headless CLI-first surface for cross-platform export/import
|
||||
|
||||
The CLI-first fork shape starts here:
|
||||
|
||||
* [Start-HeadlessIntune.ps1](/Users/avedelphina/Local/IntuneManagement/Start-HeadlessIntune.ps1)
|
||||
* [Headless/README.md](/Users/avedelphina/Local/IntuneManagement/Headless/README.md)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://twitter.com/Micke_K_72">
|
||||
@@ -64,6 +74,128 @@ The tool will by default generate the files; `BulkExport.json` and `BulkImport.j
|
||||
|
||||
The app authentication can either be passed on the command line or stored in the settings. Tennant Settings is required for multiple environments.
|
||||
|
||||
### Headless use on macOS/Linux
|
||||
|
||||
The WPF UI is still Windows-only, but the **silent batch** mode can be used headlessly with `pwsh` on macOS/Linux.
|
||||
|
||||
For non-Windows use:
|
||||
|
||||
* Run `Start-IntuneManagement.ps1` with `-Silent`
|
||||
* Use app-only authentication with `-TenantId`, `-AppId` and `-Secret` or `-Certificate`
|
||||
* Use `-JSonSettings` and preferably specify `-JSonFile`
|
||||
* Pass a batch configuration JSON with `-SilentBatchFile`
|
||||
|
||||
Common policy object type IDs:
|
||||
|
||||
* `DeviceConfiguration`
|
||||
* `SettingsCatalog`
|
||||
* `AdministrativeTemplates`
|
||||
* `CompliancePolicies`
|
||||
* `EndpointSecurity`
|
||||
* `PolicySets`
|
||||
* `ConditionalAccess`
|
||||
|
||||
**Example export batch file**
|
||||
|
||||
```json
|
||||
{
|
||||
"BulkExport": [
|
||||
{ "Name": "txtExportPath", "Value": "/tmp/intune-export" },
|
||||
{ "Name": "txtExportNameFilter", "Value": "" },
|
||||
{ "Name": "chkAddObjectType", "Value": true },
|
||||
{ "Name": "chkExportAssignments", "Value": true },
|
||||
{ "Name": "chkAddCompanyName", "Value": false },
|
||||
{
|
||||
"Name": "ObjectTypes",
|
||||
"Type": "Custom",
|
||||
"ObjectTypes": [
|
||||
"DeviceConfiguration",
|
||||
"SettingsCatalog",
|
||||
"AdministrativeTemplates",
|
||||
"CompliancePolicies",
|
||||
"EndpointSecurity",
|
||||
"PolicySets"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example export command**
|
||||
|
||||
```powershell
|
||||
pwsh ./Start-IntuneManagement.ps1 `
|
||||
-Silent `
|
||||
-JSonSettings -JSonFile "/tmp/intune-settings.json" `
|
||||
-TenantId "<source-tenant-id>" `
|
||||
-AppId "<app-id>" `
|
||||
-Secret "<client-secret>" `
|
||||
-SilentBatchFile "/tmp/BulkExport.json"
|
||||
```
|
||||
|
||||
**Example import batch file**
|
||||
|
||||
```json
|
||||
{
|
||||
"BulkImport": [
|
||||
{ "Name": "txtImportPath", "Value": "/tmp/intune-export/SourceTenantName" },
|
||||
{ "Name": "txtImportNameFilter", "Value": "" },
|
||||
{ "Name": "chkAddObjectType", "Value": true },
|
||||
{ "Name": "chkImportScopes", "Value": true },
|
||||
{ "Name": "chkImportAssignments", "Value": true },
|
||||
{ "Name": "chkReplaceDependencyIDs", "Value": true },
|
||||
{ "Name": "cbImportType", "Value": "alwaysImport" },
|
||||
{
|
||||
"Name": "ObjectTypes",
|
||||
"Type": "Custom",
|
||||
"ObjectTypes": [
|
||||
"DeviceConfiguration",
|
||||
"SettingsCatalog",
|
||||
"AdministrativeTemplates",
|
||||
"CompliancePolicies",
|
||||
"EndpointSecurity",
|
||||
"PolicySets"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example import command**
|
||||
|
||||
```powershell
|
||||
pwsh ./Start-IntuneManagement.ps1 `
|
||||
-Silent `
|
||||
-JSonSettings -JSonFile "/tmp/intune-settings.json" `
|
||||
-TenantId "<target-tenant-id>" `
|
||||
-AppId "<app-id>" `
|
||||
-Secret "<client-secret>" `
|
||||
-SilentBatchFile "/tmp/BulkImport.json"
|
||||
```
|
||||
|
||||
Wrapper scripts are also included:
|
||||
|
||||
```powershell
|
||||
pwsh ./Scripts/Export-Policies.ps1 `
|
||||
-TenantId "<source-tenant-id>" `
|
||||
-AppId "<app-id>" `
|
||||
-Secret "<client-secret>" `
|
||||
-ExportPath "/tmp/intune-export" `
|
||||
-IncludeAssignments
|
||||
```
|
||||
|
||||
```powershell
|
||||
pwsh ./Scripts/Import-Policies.ps1 `
|
||||
-TenantId "<target-tenant-id>" `
|
||||
-AppId "<app-id>" `
|
||||
-Secret "<client-secret>" `
|
||||
-ImportPath "/tmp/intune-export/SourceTenantName" `
|
||||
-ImportType alwaysImport `
|
||||
-IncludeAssignments `
|
||||
-IncludeScopeTags `
|
||||
-ReplaceDependencyIds
|
||||
```
|
||||
|
||||
**Command line example:**
|
||||
|
||||
Start-IntuneManagement.ps1 -Silent -TenantId "<*TenantID*>" -SilentBatchFile <*PathToFile*> [-AppId <*AppId*>] [-Secret <*Secret*> | -Certificate <*CertThumb*>]
|
||||
@@ -341,4 +473,4 @@ Check the log file for errors. The UI might not show errors why login failed etc
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
43
Scripts/Export-Policies.ps1
Normal file
43
Scripts/Export-Policies.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Headless Intune policy export wrapper for macOS/Linux/Windows.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExportPath,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[string[]]$ObjectTypes = @(
|
||||
"DeviceConfiguration",
|
||||
"SettingsCatalog",
|
||||
"AdministrativeTemplates",
|
||||
"CompliancePolicies",
|
||||
"EndpointSecurity",
|
||||
"PolicySets"
|
||||
),
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$AddCompanyName
|
||||
)
|
||||
|
||||
$modulePath = Join-Path (Split-Path -Parent $PSScriptRoot) "Headless/IntuneManagement.Headless.psd1"
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
Export-IntunePolicies @PSBoundParameters
|
||||
48
Scripts/Import-Policies.ps1
Normal file
48
Scripts/Import-Policies.ps1
Normal file
@@ -0,0 +1,48 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Headless Intune policy import wrapper for macOS/Linux/Windows.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ImportPath,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
|
||||
[string]$ImportType = "alwaysImport",
|
||||
|
||||
[string[]]$ObjectTypes = @(
|
||||
"DeviceConfiguration",
|
||||
"SettingsCatalog",
|
||||
"AdministrativeTemplates",
|
||||
"CompliancePolicies",
|
||||
"EndpointSecurity",
|
||||
"PolicySets"
|
||||
),
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$IncludeScopeTags,
|
||||
|
||||
[switch]$ReplaceDependencyIds
|
||||
)
|
||||
|
||||
$modulePath = Join-Path (Split-Path -Parent $PSScriptRoot) "Headless/IntuneManagement.Headless.psd1"
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
Import-IntunePolicies @PSBoundParameters
|
||||
74
Start-HeadlessIntune.ps1
Normal file
74
Start-HeadlessIntune.ps1
Normal file
@@ -0,0 +1,74 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet("Export","Import")]
|
||||
[string]$Action,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantId,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppId,
|
||||
|
||||
[string]$Secret,
|
||||
|
||||
[string]$Certificate,
|
||||
|
||||
[string]$SettingsFile,
|
||||
|
||||
[string]$BatchFile,
|
||||
|
||||
[string]$NameFilter = "",
|
||||
|
||||
[string[]]$ObjectTypes,
|
||||
|
||||
[string]$ExportPath,
|
||||
|
||||
[string]$ImportPath,
|
||||
|
||||
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
|
||||
[string]$ImportType = "alwaysImport",
|
||||
|
||||
[switch]$IncludeAssignments,
|
||||
|
||||
[switch]$AddCompanyName,
|
||||
|
||||
[switch]$IncludeScopeTags,
|
||||
|
||||
[switch]$ReplaceDependencyIds
|
||||
)
|
||||
|
||||
$modulePath = Join-Path $PSScriptRoot "Headless/IntuneManagement.Headless.psd1"
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
$invokeParams = @{
|
||||
Action = $Action
|
||||
TenantId = $TenantId
|
||||
AppId = $AppId
|
||||
SettingsFile = $SettingsFile
|
||||
BatchFile = $BatchFile
|
||||
NameFilter = $NameFilter
|
||||
ExportPath = $ExportPath
|
||||
ImportPath = $ImportPath
|
||||
ImportType = $ImportType
|
||||
IncludeAssignments = $IncludeAssignments
|
||||
AddCompanyName = $AddCompanyName
|
||||
IncludeScopeTags = $IncludeScopeTags
|
||||
ReplaceDependencyIds = $ReplaceDependencyIds
|
||||
}
|
||||
|
||||
if($PSBoundParameters.ContainsKey("ObjectTypes"))
|
||||
{
|
||||
$invokeParams.ObjectTypes = $ObjectTypes
|
||||
}
|
||||
|
||||
if($Secret)
|
||||
{
|
||||
$invokeParams.Secret = $Secret
|
||||
}
|
||||
elseif($Certificate)
|
||||
{
|
||||
$invokeParams.Certificate = $Certificate
|
||||
}
|
||||
|
||||
Invoke-IntunePolicyAction @invokeParams
|
||||
Reference in New Issue
Block a user