I’ve been playing around a little with this issue, trying to keep track of both what configurations are tied to what GUIDs, and then which machines are configured to pull those GUIDs. I opted for a SQL database approach, and came up with a couple of scripts:
Function Publish-DSCConfiguration{
Param (
[Parameter(Mandatory=$True)]
[ValidateScript({test-path $_ })]
[System.IO.FileInfo]$Path,
[String]$Description,
[Parameter(Mandatory=$True)]
[Guid]$Guid
)
Write-Verbose "Validating parameters"
If (!($Path.name -like "*.mof")){
Write-Verbose "Specified path not a MOF file. Searching directory"
$path = Get-ChildItem $path -filter *.mof
if ($path.count -eq 0){
Write-error "MOF file not found in specified Path" -ErrorAction Stop
}
Elseif ($path.count -gt 1){
Write-error "Multiple MOF files found in specified Path. Please specify correct file" -ErrorAction Stop
}
else {
Write-Verbose "MOF file found at $path"
}
}
$dest = "\\PULLSERVER\c`$\Program Files\WindowsPowerShell\DscService\Configuration\$guid.mof"
if(!(Test-Path -path (split-path -Path $dest -Parent))){
Write-Error "Cannot access destination directory $(split-path -path $dest.fullname -parent)" -erroraction Stop
}
Write-Verbose "Attempt database connection before updating files"
Try{
$SQLReadConn = New-Object System.Data.SqlClient.SqlConnection
$SQLReadConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLReadConn.Open()
$SQLWriteConn = New-Object System.Data.SqlClient.SqlConnection
$SQLWriteConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLWriteConn.Open()
} Catch{
Write-Error "Unable to connect to database, halting script." -ErrorAction Stop
}
If (test-path $dest -ea SilentlyContinue){
Write-Verbose "File exists, creating backup"
Copy $dest "$(split-path $dest -parent)\Backup\$(Split-path $dest -Leaf)-$(get-date -UFormat "%m.%d.%y-%H.%M")" -Force -ErrorAction Stop
}
copy $Path $dest -Force -ErrorAction Stop
New-DscCheckSum $dest -force -ErrorAction Stop
#Once publishing is complete, write data to tracking database
#If new config, write Configname ($path.name), GUID, creation date
#If updated config, update/write configname ($Path.name), GUID, modify date
$ConfigName = $((split-path $path -parent).split('\')[-1])
$SQLReadCmd = New-Object System.Data.SqlClient.SqlCommand
$SQLReadCmd.Connection = $SQLReadConn
$SQLReadCmd.CommandText = "SELECT * FROM ConfigList WHERE Guid='$guid'"
$result = $SQLReadCmd.ExecuteReader()
If ($result.length -eq $Null){
Write-Verbose "GUID not found in database, writing entry as new configuration"
$SQLString = "INSERT INTO ConfigList (Guid,ConfigName,CreationDate,Description) VALUES('{0}','{1}','{2}','{3}')" -f $Guid,$ConfigName,$(get-date),$Description
} Else {
Write-Verbose "Entry found in database for GUID, updating record"
$SQLString = "UPDATE ConfigList SET ConfigName = '$ConfigName', UpdateTime = '$(Get-date)', Description = '$Description' WHERE Guid = '$guid'"
}
$SQLWriteCmd = New-Object System.Data.SqlClient.SqlCommand
$SQLWriteCmd.Connection = $SQLWriteConn
$SQLWriteCMD.CommandText = $SQLString
$SQLWriteCmd.executenonquery() | Out-Null
$SQLReadConn.Close()
$SQLWriteConn.Close()
}
I’ll generate my mof like normal, then use this script to move it and rename it with a guid, then write the info into a sql database, recording GUID, creation date/time, last updated date/time, and the name of the configuration before it was moved. So for a new config the command might be this:
Publish-DSCConfiguration -Path C:\dsc\BaselineServer\localhost.mof -Guid $([GUID]::NewGuid()) -Verbose
Then once it is out on the pull server, I’ll use this code to tell a server to pull that file:
Configuration SetPullMode{
Param(
[parameter(Mandatory=$True)]
[string]$guid
)
LocalConfigurationManager{
ConfigurationMode = "ApplyAndAutoCorrect"
ConfigurationID=$guid
RefreshMode='Pull'
DownloadManagerName='WebDownloadManager'
DownloadManagerCustomData=@{
ServerUrl = 'https://PSDSCPullServerCert:8080/PSDSCPullServer.svc';
}
}
}
Function Set-DSCPullConfig{
Param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[Alias('Computername','Computer')]
[String[]]$NodeName,
[Parameter(Mandatory=$True)]
[Guid]$Guid
)
Begin{
write-verbose "Generating MOF"
$MofPath = SPlit-path -path $(SetPullMode -guid $guid -OutputPath C:\DSC\SetPullMode) -Parent
Write-Verbose "Testing database access"
Try{
$SQLReadConn = New-Object System.Data.SqlClient.SqlConnection
$SQLReadConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLReadConn.Open()
$SQLReadConn.Close()
$SQLWriteConn = New-Object System.Data.SqlClient.SqlConnection
$SQLWriteConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLWriteConn.Open()
$SQLWriteConn.Close()
} Catch{
Write-Error "Unable to connect to database, halting script." -ErrorAction Stop
}
}
Process{
Foreach ($Computer in $NodeName){
Write-Verbose "Pushing LCM config to $computer"
Copy-Item "$mofpath\localhost.meta.mof" "$mofpath\$computer.meta.mof"
Set-DscLocalConfigurationManager -ComputerName $Computer -path $MofPath -ErrorAction Stop
Write-Verbose "Updating configuration database"
$SQLReadConn = New-Object System.Data.SqlClient.SqlConnection
$SQLReadConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLReadConn.Open()
$SQLWriteConn = New-Object System.Data.SqlClient.SqlConnection
$SQLWriteConn.ConnectionString = "server=.\SQLExpress;database=DSCData;trusted_connection=true;"
$SQLWriteConn.Open()
$SQLReadCmd = New-Object System.Data.SqlClient.SqlCommand
$SQLReadCmd.Connection = $SQLReadConn
$SQLReadCmd.CommandText = "SELECT * FROM AssignList WHERE ComputerName='$Computer'"
$result = $SQLReadCmd.ExecuteReader()
If ($result.length -eq $Null){
Write-Verbose "Computer $computer not found in database, writing entry as new configuration"
$SQLString = "INSERT INTO AssignList (Guid,ComputerName,AssignDate) VALUES('{0}','{1}','{2}')" -f $Guid,$Computer,$(get-date)
} Else {
Write-Verbose "Entry found in database for $computer, updating record"
$SQLString = "UPDATE AssignList SET Guid = '$guid', AssignDate = '$(Get-date)' WHERE ComputerName = '$Computer'"
}
$SQLWriteCmd = New-Object System.Data.SqlClient.SqlCommand
$SQLWriteCmd.Connection = $SQLWriteConn
$SQLWriteCMD.CommandText = $SQLString
$SQLWriteCmd.executenonquery() | Out-Null
Write-Verbose "Closing database connections"
$SQLReadConn.Close()
$SQLWriteConn.Close()
}
#After setting the configuration update a database table
#include computername, assigned guid, date the config was pushed
}
End{
}
}
I just parameterized the GUID portion of the LCM configuration so I can pass it whatever, then I generate the MOF, push it out to the server (a push to tell it to pull, still mixes me up a bit) then I write an entry in a second table that lists the server name, the assigned guid and the date it was set.
Not super elegant or anthing, and I still need to clean up the code a bit, document, etc. but so far it seems to work.