Skip to content

Instantly share code, notes, and snippets.

@mgreenegit
Created April 30, 2022 19:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgreenegit/f67fc553b865716d1e3e69bda150bda6 to your computer and use it in GitHub Desktop.
Save mgreenegit/f67fc553b865716d1e3e69bda150bda6 to your computer and use it in GitHub Desktop.
An example DSC resource where only the functions need to be edited
# This is the Get function
function Get-Resource {
param(
[Parameter(Mandatory = $true)]
[String]
$Ensure,
[Parameter(Mandatory = $true)]
[string]
$PropertyA,
[Parameter(Mandatory = $true)]
[boolean]
$PropertyB
)
# base paths
$psBasePath = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\TEST'
if (Test-Path $psBasePath) {
# Get key value
$keyA = Get-ItemProperty $psBasePath -Name KeyA -ErrorAction SilentlyContinue
$keyB = Get-ItemProperty $psBasePath -Name KeyB -ErrorAction SilentlyContinue
}
# The Ensure property should indicate whether the machine is overall in the right state
if ($keyA.KeyA -eq $PropertyA -and $keyB.KeyB -eq $PropertyB) {
$Ensure = 'Present'
}
else {
$Ensure = 'Absent'
}
# Information returned by Get will be available in Azure via API
return @{
Ensure = $Ensure
PropertyA = $keyA.KeyA
PropertyB = $keyB.KeyB
}
}
# This is the Set function
function Set-Resource {
param(
[Parameter(Mandatory = $true)]
[String]
$Ensure,
[Parameter(Mandatory = $true)]
[string]
$PropertyA,
[Parameter(Mandatory = $true)]
[boolean]
$PropertyB
)
# base path
$psBasePath = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\TEST'
# Create key if it does not exist
if (-not (Test-Path $psBasePath)) { $null = New-Item $psBasePath -Force }
if ('Present' -eq $Ensure) {
# Write registry key
Set-ItemProperty $psBasePath -Name KEYA -Value $PropertyA
Set-ItemProperty $psBasePath -Name KEYB -Value $PropertyB
}
if ('Absent' -eq $Ensure) {
# Remove registry key
Remove-Item 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\TEST' -Force -Recurse
}
}
# This is the Test function
function Test-Resource {
param(
[Parameter(Mandatory = $true)]
[String]
$Ensure,
[Parameter(Mandatory = $true)]
[string]
$PropertyA,
[Parameter(Mandatory = $true)]
[boolean]
$PropertyB
)
$get = Get-Resource @PSBoundParameters
# The Ensure property is already testing values, there is no need to test them all again
# For some scenarios, it might be good to compare additional properties (-and)
if ($Ensure -eq $get.Ensure) {
$test = $true
}
else { $test = $false }
return $test
}
# Make Properties less intimindating to edit
# These will be inherited by the DSC resource
class DscProperties {
# Example Ensure enum workaround
[DscProperty(Key)]
[ValidateSet('Present', 'Absent')]
[String]$Ensure
# Example sring
[DscProperty()]
[ValidateNotNullOrEmpty()]
[String]$PropertyA
# Example of boolean workaround
[DscProperty()]
[ValidateSet('True', 'False')]
[String]$PropertyB
}
# This provides a simple way to show more information in phrase
function Get-AdditionalText {
'#psdsc'
}
# This class should normally not need to be updated
class Reason {
[DscProperty()]
[string] $Code
[DscProperty()]
[string] $Phrase
}
# This class should normally not need to be updated
[DscResource()]
class ConfigurationResource : DscProperties {
# This proeprty should normally not need to be updated
[DscProperty(NotConfigurable)]
[Reason[]]$Reasons
[ConfigurationResource] Get() {
# Splat properties to Get function
$DscProperties = $this.GetConfigurableDscProperties()
$get = Get-Resource @DscProperties
# Adds Reasons to get
$additionalText = Get-AdditionalText
$get.Add('Reasons', $this.FormatReasons($get,$additionalText))
# Return hashtable produced by Get function
return $get
}
[Void] Set() {
# Splat properties to Set function
$DscProperties = $this.GetConfigurableDscProperties()
Set-Resource @DscProperties
}
[Bool] Test() {
# Splat properties to Test function
$DscProperties = $this.GetConfigurableDscProperties()
return Test-Resource @DscProperties
}
[Hashtable] GetConfigurableDscProperties() {
# This method returns a hashtable of properties with two special workarounds
# The hashtable will not include any properties marked as "NotConfigurable"
# Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
# The intent is to simplify splatting to functions
$dscProperties = @{}
foreach ($property in [DscProperties].GetProperties().Name) {
# Checks if "NotConfigurable" attribute is set
$notConfigurable = [DscProperties].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
if (!$notConfigurable) {
$value = $this.$property
# Gets the list of valid values from the ValidateSet attribute
$validateSet = [DscProperties].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
if ($validateSet) {
# Workaround for boolean types
if ($null -eq (Compare-Object @('True', 'False') $validateSet)) {
$value = [System.Convert]::ToBoolean($this.$property)
}
}
# Add property to new
$dscProperties.add($property, $value)
}
}
return $dscProperties
}
[Reason[]] FormatReasons([hashtable]$get,[string]$additionalText) {
# This method takes information about a DSC resource and returns an array of Reason objects
# Including text for phrase that will render well in a browser
$dscProperties = $this.GetConfigurableDscProperties()
$state0 = $state1 = $null
foreach ($key in $dscProperties.Keys) {
if ($dscProperties.$key -eq $get.$key) {
$state0 += "`t`t[+] $key" + ':' + "`n`t`t`tExpected value to be `"$($dscProperties.$key)`"`n`t`t`tActual value was `"$($get.$key)`"`n"
}
else {
$state1 += "`t`t[-] $key" + ':' + "`n`t`t`tExpected value to be `"$($dscProperties.$key)`"`n`t`t`tActual value was `"$($get.$key)`"`n"
}
}
$Reason = [reason]::new()
$Reason.code = $this.GetType().Name + ':' + $this.GetType().Name + ':Configuration'
$phrase = "The machine returned the following configuration details.`n`n"
$phrase += "`tSettings in desired state:`n$state0`n"
$phrase += "`tSettings not in desired state:`n$state1"
$phrase += "`n$additionalText"
$Reason.phrase = $phrase
$return = @()
$return += $Reason
return $return
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment