Files
IntunePolicies/ImportPolicies.ps1

415 lines
15 KiB
PowerShell

# ==========================================
# Intune / Graph Automation Script - Idempotent
# ==========================================
# Ensure Microsoft Graph modules are installed
foreach ($module in @("Microsoft.Graph", "Microsoft.Graph.Beta")) {
if (-not (Get-Module -ListAvailable -Name $module)) {
Install-Module -Name $module -Scope CurrentUser -Force
}
}
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Device.ReadWrite.All","DeviceManagementConfiguration.ReadWrite.All","Organization.Read.All","Group.ReadWrite.All","Directory.ReadWrite.All" -NoWelcome
# Get Tenant ID
$tenant = Get-MgOrganization
$tenantId = $tenant.Id
# -------------------------
# Helper Functions
# -------------------------
# -------------------------
# Helper Functions
# -------------------------
function Get-OrCreateGroup {
param(
[string]$DisplayName,
[string]$MailNickname,
[string]$DynamicRule = $null
)
if (-not $DisplayName) { throw "DisplayName cannot be empty" }
# ----------------------------
# Fetch all groups (Graph SDK handles paging)
# ----------------------------
$allGroups = Get-MgGroup -All
$existingGroup = $allGroups | Where-Object { $_.displayName -eq $DisplayName } | Select-Object -First 1
if ($existingGroup) {
Write-Host "⚠️ Group '$DisplayName' already exists"
$null = return $existingGroup.Id
}
# ----------------------------
# Create new group
# ----------------------------
$groupBody = @{
displayName = $DisplayName
mailEnabled = $false
mailNickname = $MailNickname
securityEnabled = $true
}
if ($DynamicRule) {
$groupBody.groupTypes = @("DynamicMembership")
$groupBody.membershipRule = $DynamicRule
$groupBody.membershipRuleProcessingState = "On"
}
$response = New-MgGroup -BodyParameter $groupBody
Write-Host "✅ Created group '$DisplayName'"
$null = return $response.Id
}
function Import-ConfigurationPolicy {
param(
[string]$PolicyPath,
[string]$GroupId
)
# Load the JSON policy
$JsonData = Get-Content -Path $PolicyPath -Raw
$JsonDataUpdated = $JsonData -replace '\$tenantId', $tenantId
$PolicyObject = $JsonDataUpdated | ConvertFrom-Json
if (-not $PolicyObject.name) {
throw "Policy has no 'name': $PolicyPath"
}
# ----------------------------
# Fetch all existing configuration policies
# ----------------------------
$allPolicies = @()
$uri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies"
do {
$response = Invoke-MgGraphRequest -Method GET -Uri $uri
$responseObj = if ($response -is [string]) { $response | ConvertFrom-Json } else { $response }
if ($responseObj.value) { $allPolicies += $responseObj.value }
$uri = $responseObj.'@odata.nextLink'
} while ($uri)
# ----------------------------
# Check if a policy with the same name exists
# ----------------------------
$existing = $allPolicies | Where-Object { $_.name -eq $PolicyObject.name } | Select-Object -First 1
if ($existing) {
Write-Host "⚠️ Policy '$($PolicyObject.name)' already exists"
#Assign-PolicyToGroup -PolicyType "configurationPolicies" -PolicyId $existing.id -GroupId $GroupId
$null = return $existing.id # <-- exit immediately, no creation
}
# ----------------------------
# Create the policy if it doesn't exist
# ----------------------------
$response = Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" `
-Body ($PolicyObject | ConvertTo-Json -Depth 100) `
-ContentType "application/json"
Write-Host "✅ Policy '$($PolicyObject.name)' imported"
Assign-PolicyToGroup -PolicyType "configurationPolicies" -PolicyId $response.id -GroupId $GroupId
$null = return $response.id
}
function Assign-PolicyToGroup {
param(
[string]$PolicyType,
[string]$PolicyId,
[string]$GroupId
)
if (-not $GroupId) { return }
$uri = "https://graph.microsoft.com/beta/deviceManagement/$PolicyType/$PolicyId/assign"
$body = @{
assignments = @(
@{
target = @{
"@odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = $GroupId
}
}
)
}
Invoke-MgGraphRequest -Method POST -Uri $uri -Body ($body | ConvertTo-Json -Depth 100)
Write-Host "✅ Group assigned to $PolicyType policy ID $PolicyId"
}
function Import-CompliancePolicy {
param(
[string]$PolicyPath,
[string]$GroupId
)
$JsonData = Get-Content -Path $PolicyPath -Raw
$JsonDataUpdated = $JsonData -replace '\$tenantId', $tenantId
$PolicyObject = $JsonDataUpdated | ConvertFrom-Json
if (-not $PolicyObject.displayName) {
throw "Policy has no 'displayName': $PolicyPath"
}
# ----------------------------
# Fetch all existing compliance policies via REST (paged)
# ----------------------------
$allPolicies = @()
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies"
do {
$response = Invoke-MgGraphRequest -Method GET -Uri $uri
$responseObj = if ($response -is [string]) { $response | ConvertFrom-Json } else { $response }
if ($responseObj.value) { $allPolicies += $responseObj.value }
$uri = $responseObj.'@odata.nextLink'
} while ($uri)
# ----------------------------
# Check if a policy with the same name exists
# ----------------------------
$existing = $allPolicies | Where-Object { $_.displayName -eq $PolicyObject.displayName } | Select-Object -First 1
if ($existing) {
Write-Host "⚠️ Compliance policy '$($PolicyObject.displayName)' already exists"
#Assign-PolicyToGroup -PolicyType "deviceCompliancePolicies" -PolicyId $existing.id -GroupId $GroupId
return
}
# ----------------------------
# Create new compliance policy
# ----------------------------
$response = Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies" -Body ($PolicyObject | ConvertTo-Json -Depth 100) -ContentType "application/json"
Write-Host "✅ Compliance policy '$($PolicyObject.displayName)' imported"
Assign-PolicyToGroup -PolicyType "deviceCompliancePolicies" -PolicyId $response.id -GroupId $GroupId
}
function Get-OrCreateUpdateRing {
param(
[hashtable]$Params,
[string]$GroupId
)
if (-not $Params.displayName) { throw "Update ring must have a displayName" }
# ----------------------------
# Fetch all existing update rings via SDK
# ----------------------------
$existing = Get-MgDeviceManagementDeviceConfiguration -All | Where-Object { $_.displayName -eq $Params.displayName } | Select-Object -First 1
if ($existing) {
Write-Host "⚠️ Update ring '$($Params.displayName)' already exists"
Assign-PolicyToGroup -PolicyType "deviceConfigurations" -PolicyId $existing.id -GroupId $GroupId
$null = return $existing.id
}
# ----------------------------
# Create new update ring via SDK
# ----------------------------
try {
$response = New-MgDeviceManagementDeviceConfiguration -BodyParameter $Params
Write-Host "✅ Created update ring '$($Params.displayName)'"
Assign-PolicyToGroup -PolicyType "deviceConfigurations" -PolicyId $response.id -GroupId $GroupId
$null = return $response.id
}
catch {
Write-Host "❌ Error creating update ring '$($Params.displayName)': $_"
return $null
}
}
function Get-OrCreateDriverUpdateProfile {
param(
[hashtable]$Params,
[string]$GroupId
)
if (-not $Params.displayName) { throw "Driver update profile must have a displayName" }
# ----------------------------
# Fetch all driver update profiles via REST
# ----------------------------
$existingProfiles = @()
try {
$allProfilesRaw = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles"
if ($allProfilesRaw -is [string] -and $allProfilesRaw.TrimStart().StartsWith("{")) {
# Looks like JSON, parse it
$parsed = $allProfilesRaw | ConvertFrom-Json
if ($parsed.value) { $existingProfiles = $parsed.value }
} elseif ($allProfilesRaw -is [PSCustomObject] -and $allProfilesRaw.value) {
$existingProfiles = $allProfilesRaw.value
}
} catch {
Write-Warning "Failed to fetch driver update profiles: $_"
}
# ----------------------------
# Check if profile already exists
# ----------------------------
$existing = $existingProfiles | Where-Object { $_.displayName -eq $Params.displayName } | Select-Object -First 1
if ($existing) {
Write-Host "⚠️ Driver update profile '$($Params.displayName)' already exists"
if ($GroupId) {
Assign-PolicyToGroup -PolicyType "windowsDriverUpdateProfiles" -PolicyId $existing.id -GroupId $GroupId
}
return $existing.id
}
# ----------------------------
# Create new driver update profile
# ----------------------------
try {
$bodyJson = $Params | ConvertTo-Json -Depth 100
$response = Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles" -Body $bodyJson -ContentType "application/json"
$profileId = $null
if ($response -is [string] -and $response.TrimStart().StartsWith("{")) {
$profileId = ($response | ConvertFrom-Json).id
} elseif ($response.id) {
$profileId = $response.id
}
Write-Host "✅ Created driver update profile '$($Params.displayName)'"
if ($GroupId -and $profileId) {
Assign-PolicyToGroup -PolicyType "windowsDriverUpdateProfiles" -PolicyId $profileId -GroupId $GroupId
}
return $profileId
} catch {
Write-Error "❌ Error creating driver update profile '$($Params.displayName)': $_"
}
}
# -------------------------
# Groups
# -------------------------
$testGroupId = Get-OrCreateGroup -DisplayName "Intune - Test Machine Group" -MailNickname "IntuneTestMachineGroup"
$windowsGroupId = Get-OrCreateGroup -DisplayName "Intune - All Windows Workstations" -MailNickname "IntuneWindowsDevices" -DynamicRule '(device.deviceOSType -eq "Windows") and (device.accountEnabled -eq true) and (device.managementType -eq "MDM")'
$autopilotGroupId = Get-OrCreateGroup -DisplayName "Intune - AutoPilot Devices" -MailNickname "IntuneAutoPilotDevices" -DynamicRule '(device.devicePhysicalIDs -any (_ -startsWith "[ZTDid]"))'
# -------------------------
# Configuration Policies
# -------------------------
$policyFiles = Get-ChildItem ./policies/settingscatalog
foreach ($policy in $policyFiles) {
$null = Import-ConfigurationPolicy -PolicyPath $policy.FullName -GroupId $testGroupId
}
# -------------------------
# Compliance Policies
# -------------------------
$complianceFiles = Get-ChildItem ./policies/compliance
foreach ($policy in $complianceFiles) {
$null = Import-CompliancePolicy -PolicyPath $policy.FullName -GroupId $testGroupId
}
# -------------------------
# Windows Update Rings
# -------------------------
$updateRingPilot = @{
"@odata.type"= "#microsoft.graph.windowsUpdateForBusinessConfiguration"
"displayName"= "Win - Windows Updates - Ring 1 - Pilot"
"description"= "Devices in this ring receive updates immediately after release with 1 day grace period before a forced reboot."
"automaticUpdateMode"= "windowsDefault"
"deliveryOptimizationMode"= "userDefined"
"prereleaseFeatures"= "userDefined"
"microsoftUpdateServiceAllowed"= $true # Enables updates for Microsoft products
"driversExcluded"= $false
"qualityUpdatesDeferralPeriodInDays"= 0
"featureUpdatesDeferralPeriodInDays"= 0
"qualityUpdatesPaused"= $false
"featureUpdatesPaused"= $false
"businessReadyUpdatesOnly"= "userDefined"
"skipChecksBeforeRestart"= $false
"featureUpdatesRollbackWindowInDays"= 30
"qualityUpdatesWillBeRolledBack"= $false
"featureUpdatesWillBeRolledBack"= $false
"deadlineForFeatureUpdatesInDays"= 0
"deadlineForQualityUpdatesInDays"= 0
"deadlineGracePeriodInDays"= 1
"postponeRebootUntilAfterDeadline"= $true
"autoRestartNotificationDismissal"= "notConfigured"
"userPauseAccess"= "disabled"
"userWindowsUpdateScanAccess"= "enabled"
"updateNotificationLevel"= "defaultNotifications"
"allowWindows11Upgrade"= $false
"roleScopeTagIds"= @("0") # Scope tags (use appropriate scope tags as needed)
"supportsScopeTags"= $true
}
$null = Get-OrCreateUpdateRing -Params $updateRingPilot
$updateRingProd = @{
"@odata.type"= "#microsoft.graph.windowsUpdateForBusinessConfiguration"
"displayName"= "Win - Windows Updates - Ring 2 - Production"
"description"= "Devices in this ring receive updates 10 days after release with 2-day deadline on install with 1 day grace period before a forced reboot."
"automaticUpdateMode"= "windowsDefault"
"deliveryOptimizationMode"= "userDefined"
"prereleaseFeatures"= "userDefined"
"microsoftUpdateServiceAllowed"= $true
"driversExcluded"= $false
"qualityUpdatesDeferralPeriodInDays"= 10
"featureUpdatesDeferralPeriodInDays"= 0
"qualityUpdatesPaused"= $false
"featureUpdatesPaused"= $false
"businessReadyUpdatesOnly"= "userDefined"
"skipChecksBeforeRestart"= $false
"featureUpdatesRollbackWindowInDays"= 30
"qualityUpdatesWillBeRolledBack"= $false
"featureUpdatesWillBeRolledBack"= $false
"deadlineForFeatureUpdatesInDays"= 2
"deadlineForQualityUpdatesInDays"= 2
"deadlineGracePeriodInDays"= 1
"postponeRebootUntilAfterDeadline"= $true
"autoRestartNotificationDismissal"= "notConfigured"
"userPauseAccess"= "disabled"
"userWindowsUpdateScanAccess"= "enabled"
"updateNotificationLevel"= "defaultNotifications"
"allowWindows11Upgrade"= $false
"roleScopeTagIds"= @("0")
"supportsScopeTags"= $true
}
$null = Get-OrCreateUpdateRing -Params $updateRingProd
# -------------------------
# Driver Update Profiles
# -------------------------
$driverProfilePilot = @{
"displayName" = "Win - Drivers - Ring 1 - Pilot"
"description" = ""
"approvalType" = "automatic"
"deploymentDeferralInDays" = 0
"newUpdates" = 0
"roleScopeTagIds" = @("0")
}
$null = Get-OrCreateDriverUpdateProfile -Params $driverProfilePilot
$driverProfileProd = @{
"displayName" = "Win - Drivers - Ring 2 - Production"
"description" = ""
"approvalType" = "automatic"
"deploymentDeferralInDays" = 10
"newUpdates" = 0
"roleScopeTagIds" = @("0")
}
$null = Get-OrCreateDriverUpdateProfile -Params $driverProfileProd
# -------------------------
# Disconnect Graph
# -------------------------
$null = Disconnect-Graph -ErrorAction SilentlyContinue
Write-Host "✅ Script completed"