In my SCCM environment I have a number of Compliance Settings that use custom scripts for discovery and remediation, and recently it dawned on me that a lot of time has been spent on these and it would be good to create a backup of those scripts. It would also be useful to be able to export the scripts so they could be edited and tested before being updated in the Configuration Item itself. So I put to together this PowerShell script which does just that!
The Configuration Item scripts are stored in an XML definition, and this can be read from the SCCM database directly and parsed with PowerShell, so that’s what this script does. It will load all the Configuration Items into a datatable from a SQL query, then go through each one looking for any settings that have scripts defined. These scripts will be exported in their native file format.
You could then edit those scripts, or add the export location to your file/folder backup for an extra level of protection for your hard work!
Here you can see an example of the output for my “Java Settings” Configuration item. A subdirectory is created for the current package version, then subdirectories under that for each Configuration setting, then the discovery and remediation scripts for that setting.

Note that the script will only process Compliance Items with a CIType_ID of 3, which equates to the Operating System type you will see in the SCCM console for the Configuration Item, which is the type that may use a script as the discovery source.
Export-CMConfigurationItemScripts.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Exports all scripts (discovery and remediation) used in all SCCM Compliance Setting Configuration Items | |
.DESCRIPTION | |
This script connects to the SCCM database to retrieve all Compliance Setting Configuration Items. It then processes each item looking for | |
discovery and remediation scripts for the current (latest) version. It will export any script found into a directory structure. | |
.NOTES | |
Requirements – 'db_datareader' permission to the SCCM SQL database with the account running this script. | |
Parameters – set the parameters below as required | |
#> | |
################ | |
## PARAMETERS ## | |
################ | |
# Root directory to export the scripts to | |
$RootDirectory = "C:\temp" | |
# Name of the subdirectory to create | |
$SubDirectory = "Compliance_Settings_CI_Scripts" | |
# SCCM SQL Server (and instance where applicable) | |
$SQLServer = 'mysqlserver\inst_sccm' | |
# SCCM Database name | |
$Database = 'CM_ABC' | |
################## | |
## SCRIPT START ## | |
################## | |
# Create the subdirectory if doesn't exist | |
If (!(Test-Path "$RootDirectory\$SubDirectory")) | |
{ | |
New-Item –Path "$RootDirectory" –Name "$SubDirectory" –ItemType container | Out-Null | |
} | |
# Define the SQL query | |
$Query = " | |
Select * from dbo.v_ConfigurationItems | |
where CIType_ID = 3 | |
and IsLatest = 'true'" | |
# Run the SQL query | |
$connectionString = "Server=$SQLServer;Database=$Database;Integrated Security=SSPI" | |
$connection = New-Object –TypeName System.Data.SqlClient.SqlConnection | |
$connection.ConnectionString = $connectionString | |
$connection.Open() | |
$command = $connection.CreateCommand() | |
$command.CommandText = $Query | |
$reader = $command.ExecuteReader() | |
$ComplianceItems = New-Object –TypeName 'System.Data.DataTable' | |
$ComplianceItems.Load($reader) | |
$connection.Close() | |
# Process each compliance item returned | |
$ComplianceItems | foreach { | |
# Set some variables | |
$PackageVersion = "v $($_.SDMPackageVersion)" | |
[xml]$Digest = $_.SDMPackageDigest | |
$CIName = $Digest.ChildNodes.OperatingSystem.Annotation.DisplayName.Text | |
# Create subdirectory structure if doesn't exist: configuration item name > current package version | |
If (!(Test-Path "$RootDirectory\$SubDirectory\$CIName")) | |
{ | |
New-Item –Path "$RootDirectory\$SubDirectory" –Name "$CIName" –ItemType container | Out-Null | |
} | |
If (!(Test-Path "$RootDirectory\$SubDirectory\$CIName\$PackageVersion")) | |
{ | |
New-Item –Path "$RootDirectory\$SubDirectory\$CIName" –Name "$PackageVersion" –ItemType container | Out-Null | |
} | |
# Put each compliance item setting in XML format into an arraylist for quick processing | |
$Settings = New-Object System.Collections.ArrayList | |
$Digest.DesiredConfigurationDigest.OperatingSystem.Settings.RootComplexSetting.SimpleSetting | foreach { | |
[void]$Settings.Add([xml]$_.OuterXml) | |
} | |
# Process each compliance item setting | |
$Settings | foreach { | |
# Only process if this setting has a script source | |
If ($_.SimpleSetting.ScriptDiscoverySource) | |
{ | |
# Set some variables | |
$SettingName = $_.SimpleSetting.Annotation.DisplayName.Text | |
$DiscoveryScriptType = $_.SimpleSetting.ScriptDiscoverySource.DiscoveryScriptBody.ScriptType | |
$DiscoveryScript = $_.SimpleSetting.ScriptDiscoverySource.DiscoveryScriptBody.'#text' | |
$RemediationScriptType = $_.SimpleSetting.ScriptDiscoverySource.RemediationScriptBody.ScriptType | |
$RemediationScript = $_.SimpleSetting.ScriptDiscoverySource.RemediationScriptBody.'#text' | |
# Create the subdirectory for this setting if doesn't exist | |
If (!(Test-Path "$RootDirectory\$SubDirectory\$CIName\$PackageVersion\$SettingName")) | |
{ | |
New-Item "$RootDirectory\$SubDirectory\$CIName\$PackageVersion" –Name $SettingName –ItemType container –Force | Out-Null | |
} | |
# If a discovery script is found | |
If ($DiscoveryScript) | |
{ | |
# Set the file extension based on the script type | |
Switch ($DiscoveryScriptType) | |
{ | |
Powershell { $Extension = "ps1" } | |
JScript { $Extension = "js" } | |
VBScript { $Extension = "vbs" } | |
} | |
# Export the script to a file | |
New-Item –Path "$RootDirectory\$SubDirectory\$CIName\$PackageVersion\$SettingName" –Name "Discovery.$Extension" –ItemType file –Value $DiscoveryScript –Force | Out-Null | |
} | |
# If a remediation script is found | |
If ($RemediationScript) | |
{ | |
# Set the file extension based on the script type | |
Switch ($RemediationScriptType) | |
{ | |
Powershell { $Extension = "ps1" } | |
JScript { $Extension = "js" } | |
VBScript { $Extension = "vbs" } | |
} | |
# Export the script to a file | |
New-Item –Path "$RootDirectory\$SubDirectory\$CIName\$PackageVersion\$SettingName" –Name "Remediation.$Extension" –ItemType file –Value $RemediationScript –Force | Out-Null | |
} | |
} | |
} | |
} | |
<# For reference: CIType_IDs | |
1 Software Updates | |
2 Baseline | |
3 OS | |
4 General | |
5 Application | |
6 Driver | |
7 Uninterpreted | |
8 Software Updates Bundle | |
9 Update List | |
10 Application Model | |
11 Global Settings | |
13 Global Expression | |
14 Supported Platform | |
21 Deployment Type | |
24 Intend Install Policy | |
25 DeploymentTechnology | |
26 HostingTechnology | |
27 InstallerTechnology | |
28 AbstractConfigurationItem | |
60 Virtual Environment | |
#> |
Where is the script?
Embedded in the page as a gist, or here if you can’t see it: https://gist.githubusercontent.com/SMSAgentSoftware/1e7ecd68573b9ef067c75126d17854cd/raw/b7450a5926f20feac02f07167567ed6b86f9d183/Export-CMConfigurationItemScripts.ps1
Great export script, do you also have an import script?
I had to include a Function to remove Invalid Chars in the CI Name and the Settings Name.
If not, the folders coulnd’t be created for some CIs
$CIName = Remove-InvalidFileNameChars $CIName
$SettingName = Remove-InvalidFileNameChars $SettingName
Function Remove-InvalidFileNameChars {
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String]$Name
)
$invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ”
$re = “[{0}]” -f [RegEx]::Escape($invalidChars)
return ($Name -replace $re)
}