Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/powershell/assets/export-model/ConfigurationPolicy-model.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#deviceManagement/configurationPolicies",
"value": [
{
"isZtModelRow": true,
"id": null,
"name": null,
"description": null,
"platforms": null,
"technologies": null,
"createdDateTime": null,
"lastModifiedDateTime": null,
"settingCount": null,
"creationSource": null,
"roleScopeTagIds": [],
"isAssigned": null,
"templateReference": {
"templateId": null,
"templateFamily": null,
"templateDisplayName": null,
"templateDisplayVersion": null
},
"priorityMetaData": {
"priority": null
},
"settings": [],
"assignments": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#deviceManagement/deviceConfigurations(assignments())",
"value": [
{
"isZtModelRow": true,
"version": null,
"configDeviceHealthMonitoringCustomScope": null,
"@odata.type": null,
"id": null,
"description": null,
"allowDeviceHealthMonitoring": null,
"[email protected]": null,
"deviceManagementApplicabilityRuleDeviceMode": null,
"assignments": [],
"configDeviceHealthMonitoringScope": null,
"displayName": null,
"roleScopeTagIds": [],
"createdDateTime": null,
"deviceManagementApplicabilityRuleOsEdition": null,
"supportsScopeTags": null,
"deviceManagementApplicabilityRuleOsVersion": null,
"lastModifiedDateTime": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#deviceManagement/deviceConfigurations(assignments())",
"value": [
{
"isZtModelRow": true,
"engagedRestartSnoozeScheduleInDays": null,
"createdDateTime": null,
"installationSchedule": {
"scheduledInstallTime": null,
"@odata.type": null,
"scheduledInstallDay": null
},
"userPauseAccess": null,
"lastModifiedDateTime": null,
"skipChecksBeforeRestart": null,
"featureUpdatesPauseStartDate": null,
"driversExcluded": null,
"deviceManagementApplicabilityRuleOsVersion": null,
"description": null,
"userWindowsUpdateScanAccess": null,
"featureUpdatesRollbackStartDateTime": null,
"businessReadyUpdatesOnly": null,
"deviceManagementApplicabilityRuleDeviceMode": null,
"featureUpdatesPauseExpiryDateTime": null,
"roleScopeTagIds": [],
"updateWeeks": null,
"assignments": [],
"featureUpdatesRollbackWindowInDays": null,
"deliveryOptimizationMode": null,
"qualityUpdatesPauseExpiryDateTime": null,
"automaticUpdateMode": null,
"@odata.type": null,
"qualityUpdatesDeferralPeriodInDays": null,
"engagedRestartDeadlineInDays": null,
"autoRestartNotificationDismissal": null,
"featureUpdatesWillBeRolledBack": null,
"featureUpdatesPaused": null,
"id": null,
"version": null,
"postponeRebootUntilAfterDeadline": null,
"qualityUpdatesPauseStartDate": null,
"allowWindows11Upgrade": null,
"deviceManagementApplicabilityRuleOsEdition": null,
"scheduleImminentRestartWarningInMinutes": null,
"engagedRestartTransitionScheduleInDays": null,
"deadlineForQualityUpdatesInDays": null,
"qualityUpdatesPaused": null,
"scheduleRestartWarningInHours": null,
"updateNotificationLevel": null,
"displayName": null,
"supportsScopeTags": null,
"[email protected]": null,
"deadlineForFeatureUpdatesInDays": null,
"featureUpdatesDeferralPeriodInDays": null,
"qualityUpdatesWillBeRolledBack": null,
"qualityUpdatesRollbackStartDateTime": null,
"deadlineGracePeriodInDays": null,
"prereleaseFeatures": null,
"microsoftUpdateServiceAllowed": null
}
]
}
4 changes: 4 additions & 0 deletions src/powershell/private/export/Export-Database.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ as

if ($Pillar -in ('All', 'Devices')) {
Import-EntraTable -Database $database -ExportPath $ExportPath -TableName 'Device'
Import-EntraTable -Database $database -ExportPath $ExportPath -TableName 'ConfigurationPolicy'
Import-EntraTable -Database $database -ExportPath $ExportPath -TableName 'WindowsUpdateForBusinessConfiguration'
Import-EntraTable -Database $database -ExportPath $ExportPath -TableName 'WindowsHealthMonitoringConfiguration'
# Import-EntraTable -Database $database -ExportPath $ExportPath -TableName 'DeviceConfiguration'
}

$database
Expand Down
20 changes: 20 additions & 0 deletions src/powershell/private/export/Export-TenantData.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,25 @@ function Export-TenantData {
Export-GraphEntity -ExportPath $ExportPath -EntityName 'Device' `
-EntityUri 'beta/devices' -ProgressActivity 'Devices' `
-QueryString '$top=999' -ShowCount

Export-GraphEntity -ExportPath $ExportPath -EntityName 'ConfigurationPolicy' `
-EntityUri 'beta/deviceManagement/configurationPolicies' -ProgressActivity 'Configuration Policies' `
-QueryString '$expand=assignments,settings' # Note: $count not supported on this endpoint

Export-GraphEntity -ExportPath $ExportPath -EntityName 'DeviceConfiguration' `
-EntityUri 'beta/deviceManagement/deviceConfigurations' -ProgressActivity 'Device Configurations' `
-QueryString '$expand=assignments' -ShowCount

# Extract Windows Update for Business configurations
Split-GraphEntity -ExportPath $ExportPath `
-SourceEntityName 'DeviceConfiguration' `
-TargetEntityName 'WindowsUpdateForBusinessConfiguration' `
-ResourceType '#microsoft.graph.windowsUpdateForBusinessConfiguration'

# Extract Windows Health Monitoring configurations
Split-GraphEntity -ExportPath $ExportPath `
-SourceEntityName 'DeviceConfiguration' `
-TargetEntityName 'WindowsHealthMonitoringConfiguration' `
-ResourceType '#microsoft.graph.windowsHealthMonitoringConfiguration'
}
}
91 changes: 91 additions & 0 deletions src/powershell/private/export/Split-GraphEntity.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function Split-GraphEntity {
<#
.SYNOPSIS
Extracts resources of a specific OData type from exported JSON files.
.DESCRIPTION
Reads JSON files for a given entity, filters resources by the specified @odata.type,
and exports them to a new folder/files maintaining the original structure.
.PARAMETER ExportPath
The root export path.
.PARAMETER SourceEntityName
The name of the source entity (folder name), e.g., "DeviceConfiguration".
.PARAMETER TargetEntityName
The name of the target entity (folder name), e.g., "WindowsUpdateForBusinessConfiguration".
.PARAMETER ResourceType
The OData type to filter by (e.g., "#microsoft.graph.windowsUpdateForBusinessConfiguration").
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$ExportPath,

[Parameter(Mandatory = $true)]
[string]$SourceEntityName,

[Parameter(Mandatory = $true)]
[string]$TargetEntityName,

[Parameter(Mandatory = $true)]
[string]$ResourceType
)

$sourcePath = Join-Path $ExportPath $SourceEntityName
$targetPath = Join-Path $ExportPath $TargetEntityName

if (-not (Test-Path $sourcePath)) {
Write-PSFMessage "Source path not found: $sourcePath" -Level Warning
return
}

# Create target directory
if (-not (Test-Path $targetPath)) {
New-Item -ItemType Directory -Path $targetPath -Force | Out-Null
} else {
# Clear existing files in target
Get-ChildItem -Path $targetPath -Filter "*.json" | Remove-Item -Force
}

$files = Get-ChildItem -Path $sourcePath -Filter "$SourceEntityName-*.json"

foreach ($file in $files) {
Write-PSFMessage "Processing $($file.Name) for $TargetEntityName..." -Level Verbose

try {
$jsonContent = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
}
catch {
Write-PSFMessage "Failed to parse JSON from $($file.FullName)" -Level Error
continue
}

if (-not $jsonContent.PSObject.Properties['value']) {
continue
}

$filteredResources = @($jsonContent.value | Where-Object { $_.'@odata.type' -eq $ResourceType })

if ($filteredResources.Count -gt 0) {
# Create a new ordered dictionary to preserve structure
$newProperties = [ordered]@{}

# Copy all properties from the original object (like @odata.context, @odata.nextLink)
foreach ($prop in $jsonContent.PSObject.Properties) {
if ($prop.Name -eq 'value') {
$newProperties['value'] = $filteredResources
} else {
$newProperties[$prop.Name] = $prop.Value
}
}

$outputData = [PSCustomObject]$newProperties

# Generate filename: TargetEntityName-Index.json
$suffix = $file.Name.Substring($SourceEntityName.Length) # e.g. "-0.json"
$outputFilename = "$TargetEntityName$suffix"
$outputPath = Join-Path -Path $targetPath -ChildPath $outputFilename

Write-PSFMessage "Exporting $($filteredResources.Count) items to $outputFilename" -Level Verbose
$outputData | ConvertTo-Json -Depth 100 | Set-Content -Path $outputPath
}
}
}