How to Access Desired State Configuration MOF Metadata

I’ve been doing quite a bit with PowerShell and Desired State Configuration (DSC) over the last few months, and I expect that many of you have been as well. As you’ve probably realized, there are still many areas where DSC could use a little help. For now, many of these gaps are being filled by the PowerShell community. One of the gaps that I think needs to be filled in is managing MOF files via their metadata.

When you use the PowerShell cmdlets to create a DSC configuration, you’ll end up with an industry standard MOF. Something that might look like this;

/*
@TargetNode='CHI-CORE01'
@GeneratedBy=Jeff
@GenerationDate=01/21/2015 11:35:58
@GenerationHost=WIN81-ENT-01
*/
instance of MSFT_RoleResource as $MSFT_RoleResource1ref
{
ResourceID = "[WindowsFeature]Telnet-Client";
IncludeAllSubFeature = True;
Ensure = "Present";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::8::3::WindowsFeature";
Name = "Telnet-Client";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of MSFT_RoleResource as $MSFT_RoleResource2ref
{
ResourceID = "[WindowsFeature]Windows-Server-Backup";
IncludeAllSubFeature = True;
Ensure = "Present";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::8::3::WindowsFeature";
Name = "Windows-Server-Backup";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of MSFT_ServiceResource as $MSFT_ServiceResource1ref
{
ResourceID = "[Service]bits";
State = "Running";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::18::3::Service";
Name = "bits";
StartupType = "Automatic";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of MSFT_ServiceResource as $MSFT_ServiceResource2ref
{
ResourceID = "[Service]remoteregistry";
State = "Running";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::18::3::Service";
Name = "remoteregistry";
StartupType = "Automatic";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of MSFT_ServiceResource as $MSFT_ServiceResource3ref
{
ResourceID = "[Service]wuauserv";
State = "Running";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::18::3::Service";
Name = "wuauserv";
StartupType = "Automatic";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of MSFT_RoleResource as $MSFT_RoleResource3ref
{
ResourceID = "[WindowsFeature]RSAT-AD-Powershell";
IncludeAllSubFeature = True;
Ensure = "Present";
SourceInfo = "C:\\Scripts\\DemoConfigData.ps1::46::5::WindowsFeature";
Name = "RSAT-AD-PowerShell";
ModuleName = "PSDesiredStateConfiguration";
ModuleVersion = "1.0";
};
instance of OMI_ConfigurationDocument
{
Version="1.0.0";
Author="Jeff";
GenerationDate="01/21/2015 11:35:58";
GenerationHost="WIN81-ENT-01";
};

The text at the beginning, which is a comment, and the OMI-ConfigurationDocument at the end, comprise the document’s metadata. Why should this matter?

When you set up a pull server, the MOF for a given node is copied to the server and renamed with a GUID that matches the node’s ConfigurationID, which is set in the Local Configuration Manager. If you were to do a directory listing of a pull server, it is almost impossible to know who the files belong to.
031615 1958 GetDSCMOFMe1
I’m not very good at memorizing GUIDs. What would be helpful I think would be to pipe these MOF files to a PowerShell function that would tell me who they belong to along with some other information like versioning. Here’s the function I came up with.

#requires -version 4.0
#get MOF metadata
Function Get-MOFMetadata {
[cmdletbinding()]
Param(
[Parameter(Position=0,
Mandatory,
HelpMessage="Enter the path to a MOF file",
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[ValidateScript({Test-Path $_})]
[Alias("PSPath")]
[string]$Path
)
Begin {
Write-Verbose "Starting $($MyInvocation.Mycommand)"
} #begin
Process {
Write-Verbose "Processing $path"
#read the MOF file into a variable
$content = Get-Content -Path $Path -ReadCount 0
#create an ordered hashtable
$hashProperties = [ordered]@{}
#get first 4 lines
Write-Verbose "Getting comment header"
$meta = $content | Select -Skip 1 -first 4
foreach ($item in $meta) {
#split each line
$split = $item.split("=")
$Name = $split[0].Replace("@","")
$value = $split[1].Replace("'","")
#test if a value is a datetime
[ref]$r = Get-Date
if ([datetime]::TryParse($value,$r)) {
#replace value with $r which will now be the
#value from the MOF treated as a datetime object
$value = $r.value
}
#add each element to the hashtable removing extra characters
$hashProperties.Add($name,$value)
}
#get version information
#getting more context than necessary in case you want to include
#other information
$OMIDoc = $Content| Select-String "OMI_ConfigurationDocument" -Context 6 |
Select -ExpandProperty Context
#get version string
if (($OMIDoc.PostContext | Select-String version).ToString().trim() -match "\d+\.\d+\.\d") {
$Version = $($matches.Values[0])
}
else {
$version = "Unknown"
}
$hashProperties.add("Version",$Version)
#add file information
Write-Verbose "Getting file information"
$file = Get-Item -Path $Path
$hashProperties.Add("LastModified",$file.LastWriteTime)
$hashProperties.Add("Name",$file.name)
$hashProperties.Add("Size",$file.Length)
$hashProperties.Add("Path",$file.FullName)
Write-Verbose "Creating output object"
New-Object -TypeName PSObject -Property $hashProperties
} #end process
End {
Write-Verbose "Ending $($MyInvocation.Mycommand)"
} #end
} #end Get-MOFMetadata

It is possible that you can use the .NET Framework to work with these documents, but these solutions are likely more complicated than what I’d like to deal with. My code uses common PowerShell cmdlets and techniques that an IT pro should recognize. Assuming you haven’t changed the beginning or end of the files, then you should be able to use my function.
I can get the metadata by using Select-Object to get the first few lines, skipping the first one.

$meta = $content | Select -Skip 1 -first 4

For each line, I can split the item on the = sign. I need to get rid of the @ and ‘ characters so I’ll replace each with a blank

$split = $item.split("=")
$Name  = $split[0].Replace("@","")
$value = $split[1].Replace("'","")

Because I’m going to be writing an object to the pipeline, I thought it would be nice if the date value was treated as a datetime object.

#test if a value is a datetime
[ref]$r = Get-Date
if ([datetime]::TryParse($value,$r)) {
#replace value with $r which will now be the
#value from the MOF treated as a datetime object
$value = $r.value
}

Each of these properties is added to an ordered hashtable. I do some similar string parsing with the OMI document section.

$OMIDoc = $Content| Select-String "OMI_ConfigurationDocument" -Context 6 |     Select -ExpandProperty Context

Notice that I’m using Select-String’s Context parameter. This allows you to capture the specified number of lines before and after the matching string. In my case, I want six lines. Actually, all I really want are the lines after the matching string, and specifically the version information.

#get version string
if (($OMIDoc.PostContext | Select-String version).ToString().trim() -match "\d+\.\d+\.\d") {
$Version = $($matches.Values[0])
}
else {
$version = "Unknown"
}

I didn’t want to search the entire document because there could be other instances of “Version”, and I wanted the one that was part of the OMI document section. The PostContext property will be the six lines of text following the matching string. This helps me narrow down the selection. The last bit I thought would be helpful is to include some basic file information which I can retrieve with Get-Item.

$file = Get-Item -Path $Path
$hashProperties.Add("LastModified",$file.LastWriteTime)
$hashProperties.Add("Name",$file.name)
$hashProperties.Add("Size",$file.Length)
$hashProperties.Add("Path",$file.FullName)

After that all that remains is to turn the hashtable into a custom object. Armed with this function in my console, the directory listing is now much more meaningful.

dir \\chi-web02\c$\program` files\windowspowershell\dscService\configuration\*.mof | get-mofmetadata | out-gridview -title "MOF Report"

031615 1958 GetDSCMOFMe2

As you can only have one MOF per node, this could be a handy way of identifying duplicate or obsolete MOF files. Because the function works like any other PowerShell command, you can do whatever you want with the output.

dir \\chi-web02\c$\program` files\windowspowershell\dscService\configuration\*.mof |
get-mofmetadata | Sort TargetNode,LastModified -Descending |
Select TargetNode,Size,GeneratedBy,GenerationDate,Version | format-table -AutoSize

031615 1958 GetDSCMOFMe3
I hope this helps you get a handle on your MOF files. If you are still learning and getting started with DSC, I have a few courses on Pluralsight on the topic that should be helpful. In the meantime, are you using DSC? What sort of gaps are you running into it? I hope you’ll let me know via the comments.