PSFrameworkDsc.psm1
$script:ModuleRoot = $PSScriptRoot enum Scope { SystemDefault SystemEnforced } enum ConfigType { NotSpecified Bool Int UInt Int16 Int32 Int64 UInt16 UInt32 UInt64 Double String TimeSpan DateTime ConsoleColor BoolArray IntArray UIntArray Int16Array Int32Array Int64Array UInt16Array UInt32Array UInt64Array DoubleArray StringArray TimeSpanArray DateTimeArray ConsoleColorArray PsfConfig } class ConfigHelper { [Scope] $Scope [hashtable]$Config = @{ } ConfigHelper([Scope]$Scope) { $this.Scope = $Scope } [void]Load() { $providerProps = 'PSPath','PSParentPath','PSChildName','PSDrive','PSProvider' switch ($this.Scope) { SystemDefault { if (-not (Test-Path -Path $script:config.PathDefault)) { $this.Config = @{} return } $newConfig = @{} $regItem = Get-ItemProperty -Path $script:config.PathDefault foreach ($property in $regItem.PSObject.Properties) { if ($property.Name -in $providerProps) { continue } $newConfig[$property.Name] = $property.Value } $this.Config = $newConfig } SystemEnforced { if (-not (Test-Path -Path $script:config.PathEnforced)) { $this.Config = @{} return } $newConfig = @{} $regItem = Get-ItemProperty -Path $script:config.PathEnforced foreach ($property in $regItem.PSObject.Properties) { if ($property.Name -in $providerProps) { continue } $newConfig[$property.Name] = $property.Value } $this.Config = $newConfig } default { throw "Scope not implemented: $($this.Scope)" } } } [void]Write([string]$FullName, [object]$Value, [bool]$IsConfigString = $false) { $valueToWrite = $Value if (-not $IsConfigString) { $valueToWrite = [ConfigHelper]::Serialize($Value) } if ($valueToWrite -eq $this.Config[$FullName]) { return } switch ($this.Scope) { SystemDefault { if (-not (Test-Path -Path $script:config.PathDefault)) { $null = New-Item -Path $script:config.PathDefault -Force } Set-ItemProperty -Path $script:config.PathDefault -Value $valueToWrite -Name $FullName -Force } SystemEnforced { if (-not (Test-Path -Path $script:config.PathEnforced)) { $null = New-Item -Path $script:config.PathEnforced -Force } Set-ItemProperty -Path $script:config.PathEnforced -Value $valueToWrite -Name $FullName -Force } default { throw "Scope not implemented: $($this.Scope)" } } $this.Load() } [void]Remove([string]$FullName) { if ($this.Config.Keys -notcontains $FullName) { return } switch ($this.Scope) { SystemDefault { $fail = $null Remove-ItemProperty -Path $script:config.PathDefault -Name $FullName -Force -ErrorAction SilentlyContinue -ErrorVariable fail if ($fail -and $fail.CategoryInfo.Category -ne 'InvalidArgument') { throw $fail } } SystemEnforced { $fail = $null Remove-ItemProperty -Path $script:config.PathEnforced -Name $FullName -Force -ErrorAction SilentlyContinue -ErrorVariable fail if ($fail -and $fail.CategoryInfo.Category -ne 'InvalidArgument') { throw $fail } } default { throw "Scope not implemented: $($this.Scope)" } } } [object]GetConverted([string]$FullName) { if ($this.Config.Keys -notcontains $FullName) { return $null } return [ConfigHelper]::Deserialize($this.Config[$FullName]) } [bool]Compare([object]$Value, [string]$FullName) { return $this.Compare($Value, $FullName, { $null -eq $args[0] }) } [bool]Compare([object]$Value, [string]$FullName, [scriptblock]$NullCondition = { $null -eq $args[0] }) { $isNull = & $NullCondition $Value if ($isNull) { return $this.Config.Keys -notcontains $FullName } $resValue = [ConfigHelper]::Serialize($Value) return $resValue -eq $this.Config[$FullName] } [void]Apply([string]$FullName, [object]$Value) { $this.Apply($FullName, $Value, { $null -eq $args[0] }) } [void]Apply([string]$FullName, [object]$Value, [scriptblock]$NullCondition) { $isNull = & $NullCondition $Value if ($isNull) { if ($this.Config.Keys -contains $FullName) { $this.Remove($FullName) } return } $this.Write($FullName, $Value, $false) } #region Statics static [object] Convert([string]$Value, [ConfigType]$ValueType) { switch ($ValueType) { NotSpecified { return $Value } Bool { return $Value -eq 'true' -or $Value -eq '1' } Int { return [int]$Value } UInt { return [uint32]$Value } Int16 { return [Int16]$Value } Int32 { return [Int32]$Value } Int64 { return [Int64]$Value } UInt16 { return [UInt16]$Value } UInt32 { return [UInt32]$Value } UInt64 { return [Uint64]$Value } Double { return [double]$Value } String { return $Value } TimeSpan { return [Timespan]$Value } DateTime { return [DateTime]$Value } ConsoleColor { return [ConsoleColor]$Value } BoolArray { return [ConfigHelper]::ConvertArray($Value, 'Bool') } IntArray { return [ConfigHelper]::ConvertArray($Value, 'Int') } UIntArray { return [ConfigHelper]::ConvertArray($Value, 'UInt') } Int16Array { return [ConfigHelper]::ConvertArray($Value, 'Int16') } Int32Array { return [ConfigHelper]::ConvertArray($Value, 'Int32') } Int64Array { return [ConfigHelper]::ConvertArray($Value, 'Int64') } UInt16Array { return [ConfigHelper]::ConvertArray($Value, 'UInt16') } UInt32Array { return [ConfigHelper]::ConvertArray($Value, 'UInt32') } UInt64Array { return [ConfigHelper]::ConvertArray($Value, 'UInt64') } DoubleArray { return [ConfigHelper]::ConvertArray($Value, 'Double') } StringArray { return [ConfigHelper]::ConvertArray($Value, 'String') } TimeSpanArray { return [ConfigHelper]::ConvertArray($Value, 'TimeSpan') } DateTimeArray { return [ConfigHelper]::ConvertArray($Value, 'DateTime') } ConsoleColorArray { return [ConfigHelper]::ConvertArray($Value, 'ConsoleColor') } PsfConfig { return $Value } default { return $Value } } # PowerShell Parser does not detect when there is no path in a switch statement that ends with a return. throw "Unhandled conversion error: Developer failed to do it right." } static hidden [object] ConvertArray([string]$Value, [ConfigType]$ValueType) { if ($Value -match 'þ') { $values = $Value -split 'þ' } else { $values = $Value -split '\|' } $results = foreach ($item in $Values) { [ConfigHelper]::Convert($item, $ValueType) } return @($results) } static [bool] IsLegalType($Object) { $typeName = $Object.GetType().FullName if ($typeName -eq "System.Object[]") { foreach ($item in $Object) { if (-not ([ConfigHelper]::IsLegalType($item))) { return $false } } return $true } $legalTypes = @( 'System.Boolean', 'System.Int16', 'System.Int32', 'System.Int64', 'System.UInt16', 'System.UInt32', 'System.UInt64', 'System.Double', 'System.String', 'System.TimeSpan', 'System.DateTime', 'System.ConsoleColor' ) if ($legalTypes -contains $typeName) { return $true } return $false } static [string] Serialize($Object) { switch ($Object.GetType().FullName) { "System.Object[]" { $list = @() foreach ($item in $Object) { $list += [ConfigHelper]::Serialize($item) } return "array:$($list -join "þþþ")" } "System.Boolean" { if ($Object) { return "bool:true" } else { return "bool:false" } } "System.Int16" { return "int:$Object" } "System.Int32" { return "int:$Object" } "System.Int64" { return "long:$Object" } "System.UInt16" { return "int:$Object" } "System.UInt32" { return "int:$Object" } "System.UInt64" { return "long:$Object" } "System.Double" { return "double:$Object" } "System.String" { return "string:$Object" } "System.TimeSpan" { return "timespan:$($Object.Ticks)" } "System.DateTime" { return "datetime:$($Object.Ticks)" } "System.ConsoleColor" { return "consolecolor:$Object" } default { if ($_ -notmatch '\[\]$') { throw "$_ was not recognized as a legal type!" } $list = @() foreach ($item in $Object) { $list += [ConfigHelper]::Serialize($item) } return "array:$($list -join "þþþ")" } } return "<illegal data>" } static [object] DeSerialize([string]$Item) { $index = $Item.IndexOf(":") if ($index -lt 1) { throw "No type identifier found!" } $type = $Item.Substring(0, $index).ToLower() $content = $Item.Substring($index + 1) switch ($type) { "bool" { if ($content -eq "true") { return $true } if ($content -eq "1") { return $true } if ($content -eq "false") { return $false } if ($content -eq "0") { return $false } throw "Failed to interpret as bool: $content" } "int" { return ([int]$content) } "double" { return [double]$content } "long" { return [long]$content } "string" { return $content } "timespan" { return (New-Object System.TimeSpan($content)) } "datetime" { return (New-Object System.DateTime($content)) } "consolecolor" { return ([System.ConsoleColor]$content) } "array" { $list = @() foreach ($item in ($content -split "þþþ")) { $list += [ConfigHelper]::Deserialize($item) } return $list } default { throw "Unknown type identifier" } } return $null } #endregion Statics } class ExtendedConfigHelper { # Matching Property-Names to conditional logic to remove from config [hashtable]$Properties [string]$BasePath [object]$Item [Scope]$Scope [ConfigHelper]$Helper ExtendedConfigHelper([object]$Item, [Scope]$Scope, [string]$BasePath, [hashtable]$Properties) { $this.Item = $item $this.Scope = $Scope $this.BasePath = $BasePath $this.Properties = $Properties $this.Helper = [ConfigHelper]::new($Scope) } [bool]Test() { $this.Helper.Load() foreach ($property in $this.Properties.Keys) { if (-not $this.Helper.Compare($this.Item.$property, "$($this.BasePath).$property", $this.Properties.$property)) { return $false } } return $true } [hashtable]Get() { $this.Helper.Load() $result = @{} foreach ($property in $this.Properties.Keys) { if ($this.Helper.Config.Keys -contains "$($this.BasePath).$property") { $result[$property] = $this.Helper.Config."$($this.BasePath).$property" } } return $result } [void]Set() { $this.Helper.Load() foreach ($property in $this.Properties.Keys) { $this.Helper.Apply("$($this.BasePath).$property", $this.Item.$property, $this.Properties.$property) } } [void]Clear() { $this.Helper.Load() foreach ($property in $this.Properties.Keys) { $this.Helper.Remove("$($this.BasePath).$property") } } } enum Ensure { Absent Present } class Reason { [DscProperty()] [string] $Code [DscProperty()] [string] $Phrase } class PsfLoggingHelper { [string] $ProviderName [string] $InstanceName [object] $LogConfig PsfLoggingHelper([object]$LogObject, [string]$ProviderName) { $this.LogConfig = $LogObject $this.ProviderName = $ProviderName $this.InstanceName = $LogObject.InstanceName } [hashtable]Get() { if (-not $this.InstanceName) { throw "Cannot read Instance configuration - no instance defined yet!" } $helper = [ConfigHelper]::new('SystemDefault') $helper.Load() $data = @{ } $baseName = "LoggingProvider.$($this.ProviderName).$($this.InstanceName)" $names = 'Enabled', 'ExcludeFunctions', 'ExcludeModules', 'ExcludeTags', 'IncludeFunctions', 'IncludeModules', 'IncludeTags', 'MaxLevel', 'MinLevel' foreach ($name in $names) { if ($helper.Config.Keys -notcontains "$baseName.$name") { continue } $data[$name] = $helper.Config["$baseName.$name"] } return $data } [bool]Test() { $helper = [ConfigHelper]::new('SystemDefault') $helper.Load() $baseName = "LoggingProvider.$($this.ProviderName).$($this.InstanceName)" if ($this.LogConfig.Enabled -ne $helper.GetConverted("$baseName.Enabled")) { return $false } if (-not $helper.Compare($this.LogConfig.MinLevel, "$baseName.MinLevel", { $args[0] -lt 1 })) { return $false } if (-not $helper.Compare($this.LogConfig.MaxLevel, "$baseName.MaxLevel", { $args[0] -lt 1 })) { return $false } $names = 'ExcludeFunctions', 'ExcludeModules', 'ExcludeTags', 'IncludeFunctions', 'IncludeModules', 'IncludeTags' foreach ($name in $names) { if (-not $helper.Compare($this.LogConfig.$name, "$baseName.$name")) { return $false } } return $true } [void]Set() { $helper = [ConfigHelper]::new('SystemDefault') $helper.Load() $baseName = "LoggingProvider.$($this.ProviderName).$($this.InstanceName)" if ($this.LogConfig.Ensure -eq 'Absent') { $names = 'Enabled', 'ExcludeFunctions', 'ExcludeModules', 'ExcludeTags', 'IncludeFunctions', 'IncludeModules', 'IncludeTags', 'MaxLevel', 'MinLevel' foreach ($name in $names) { $helper.Remove("$baseName.$name") } return } $helper.Write("$baseName.Enabled", $this.LogConfig.Enabled, $false) $helper.Apply("$baseName.MinLevel", $this.LogConfig.MinLevel, { $args[0] -lt 1 }) $helper.Apply("$baseName.MaxLevel", $this.LogConfig.MaxLevel, { $args[0] -lt 1 }) $names = 'ExcludeFunctions', 'ExcludeModules', 'ExcludeTags', 'IncludeFunctions', 'IncludeModules', 'IncludeTags' foreach ($name in $names) { $helper.Apply("$baseName.$name", $this.LogConfig.$name) } } [void]Clear() { $helper = [ConfigHelper]::new('SystemDefault') $helper.Load() $baseName = "LoggingProvider.$($this.ProviderName).$($this.InstanceName)" $names = 'Enabled', 'ExcludeFunctions', 'ExcludeModules', 'ExcludeTags', 'IncludeFunctions', 'IncludeModules', 'IncludeTags', 'MaxLevel', 'MinLevel' foreach ($name in $names) { $helper.Remove("$baseName.$name") } } } [DscResource()] class PSFrameworkConfig { #region DSC Properties [DscProperty(Key)] [string]$FullName [DscProperty(Mandatory)] [Ensure]$Ensure <# The scope it is set to. Defaults to "SystemDefault" DSC can only access the System wide settings. #> [DscProperty()] [Scope]$ConfigScope = 'SystemDefault' <# The value to apply Is only mandatory when $Ensure is set to "Present" #> [DscProperty()] [string]$Value <# The type of the value to apply. Tries to parse out the from the string value provided. Use PsfConfig to provide the literal notation used by PSFramework for configuration. E.G. the result from this: [PSFramework.Configuration.ConfigurationHost]::ConvertToPersistedValue(42).TypeQualifiedPersistedValue Which would translate to: Int:42 #> [DscProperty()] [ConfigType]$ValueType [DscProperty(NotConfigurable)] [Reason[]] $Reasons # Reserved for Azure Guest Configuration #endregion DSC Properties [void]Set() { $param = $this.GetConfigurableDscProperties() Set-PSFrameworkConfig @param } [PSFrameworkConfig]Get() { $param = $this.GetConfigurableDscProperties() return Get-PSFrameworkConfig @param } [bool]Test() { $param = $this.GetConfigurableDscProperties() $current = $this.Get() return Test-PSFrameworkConfig @param -Current $current } [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 # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44 $dscProperties = @{} foreach ($property in [PSFrameworkConfig].GetProperties().Name) { # Checks if "NotConfigurable" attribute is set $notConfigurable = [PSFrameworkConfig].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable if (!$notConfigurable) { $paramValue = $this.$property # Gets the list of valid values from the ValidateSet attribute $validateSet = [PSFrameworkConfig].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)) { $paramValue = [System.Convert]::ToBoolean($this.$property) } } # Add property to new $dscProperties.add($property, $paramValue) } } return $dscProperties } static [object] Convert([string]$Value, [ConfigType]$ValueType) { switch ($ValueType) { NotSpecified { return $Value } Bool { return $Value -eq 'true' -or $Value -eq '1' } Int { return [int]$Value } UInt { return [uint32]$Value } Int16 { return [Int16]$Value } Int32 { return [Int32]$Value } Int64 { return [Int64]$Value } UInt16 { return [UInt16]$Value } UInt32 { return [UInt32]$Value } UInt64 { return [Uint64]$Value } Double { return [double]$Value } String { return $Value } TimeSpan { return [Timespan]$Value } DateTime { return [DateTime]$Value } ConsoleColor { return [ConsoleColor]$Value } BoolArray { return [PSFrameworkConfig]::ConvertArray($Value, 'Bool') } IntArray { return [PSFrameworkConfig]::ConvertArray($Value, 'Int') } UIntArray { return [PSFrameworkConfig]::ConvertArray($Value, 'UInt') } Int16Array { return [PSFrameworkConfig]::ConvertArray($Value, 'Int16') } Int32Array { return [PSFrameworkConfig]::ConvertArray($Value, 'Int32') } Int64Array { return [PSFrameworkConfig]::ConvertArray($Value, 'Int64') } UInt16Array { return [PSFrameworkConfig]::ConvertArray($Value, 'UInt16') } UInt32Array { return [PSFrameworkConfig]::ConvertArray($Value, 'UInt32') } UInt64Array { return [PSFrameworkConfig]::ConvertArray($Value, 'UInt64') } DoubleArray { return [PSFrameworkConfig]::ConvertArray($Value, 'Double') } StringArray { return [PSFrameworkConfig]::ConvertArray($Value, 'String') } TimeSpanArray { return [PSFrameworkConfig]::ConvertArray($Value, 'TimeSpan') } DateTimeArray { return [PSFrameworkConfig]::ConvertArray($Value, 'DateTime') } ConsoleColorArray { return [PSFrameworkConfig]::ConvertArray($Value, 'ConsoleColor') } PsfConfig { return $Value } default { return $Value } } # PowerShell Parser does not detect when there is no path in a switch statement that ends with a return. throw "Unhandled conversion error: Developer failed to do it right." } static hidden [object] ConvertArray([string]$Value, [ConfigType]$ValueType) { if ($Value -match 'þ') { $values = $Value -split 'þ' } else { $values = $Value -split '\|' } $results = foreach ($item in $Values) { [PSFrameworkConfig]::Convert($item, $ValueType) } return @($results) } static [bool] IsLegalType($Object) { $typeName = $Object.GetType().FullName if ($typeName -eq "System.Object[]") { foreach ($item in $Object) { if (-not ([PSFrameworkConfig]::IsLegalType($item))) { return $false } } return $true } $legalTypes = @( 'System.Boolean', 'System.Int16', 'System.Int32', 'System.Int64', 'System.UInt16', 'System.UInt32', 'System.UInt64', 'System.Double', 'System.String', 'System.TimeSpan', 'System.DateTime', 'System.ConsoleColor' ) if ($legalTypes -contains $typeName) { return $true } return $false } static [string] Serialize($Object) { switch ($Object.GetType().FullName) { "System.Object[]" { $list = @() foreach ($item in $Object) { $list += [PSFrameworkConfig]::Serialize($item) } return "array:$($list -join "þþþ")" } "System.Boolean" { if ($Object) { return "bool:true" } else { return "bool:false" } } "System.Int16" { return "int:$Object" } "System.Int32" { return "int:$Object" } "System.Int64" { return "long:$Object" } "System.UInt16" { return "int:$Object" } "System.UInt32" { return "int:$Object" } "System.UInt64" { return "long:$Object" } "System.Double" { return "double:$Object" } "System.String" { return "string:$Object" } "System.TimeSpan" { return "timespan:$($Object.Ticks)" } "System.DateTime" { return "datetime:$($Object.Ticks)" } "System.ConsoleColor" { return "consolecolor:$Object" } default { throw "$($Object.GetType().FullName) was not recognized as a legal type!" } } return "<illegal data>" } static [object] DeSerialize([string]$Item) { $index = $Item.IndexOf(":") if ($index -lt 1) { throw "No type identifier found!" } $type = $Item.Substring(0, $index).ToLower() $content = $Item.Substring($index + 1) switch ($type) { "bool" { if ($content -eq "true") { return $true } if ($content -eq "1") { return $true } if ($content -eq "false") { return $false } if ($content -eq "0") { return $false } throw "Failed to interpret as bool: $content" } "int" { return ([int]$content) } "double" { return [double]$content } "long" { return [long]$content } "string" { return $content } "timespan" { return (New-Object System.TimeSpan($content)) } "datetime" { return (New-Object System.DateTime($content)) } "consolecolor" { return ([System.ConsoleColor]$content) } "array" { $list = @() foreach ($item in ($content -split "þþþ")) { $list += [PSFrameworkConfig]::Deserialize($item) } return $list } default { throw "Unknown type identifier" } } return $null } } [DscResource()] class PSFrameworkLogEventLog { [DscProperty(Mandatory)] [Ensure]$Ensure #region Logging Provider Settings [DscProperty(Key)] [string]$InstanceName [DscProperty()] [string]$LogName [DscProperty()] [string]$Source [DscProperty()] [bool]$UseFallback = $true [DscProperty()] [int]$Category [DscProperty()] [int]$InfoID [DscProperty()] [int]$WarningID [DscProperty()] [int]$ErrorID [DscProperty()] [string]$ErrorTag [DscProperty()] [string]$TimeFormat [DscProperty()] [bool]$NumericTagAsID #endregion Logging Provider Settings #region Common Logging Settings [DscProperty()] [bool]$Enabled = $true [DscProperty()] [string[]]$IncludeModules [DscProperty()] [string[]]$ExcludeModules [DscProperty()] [string[]]$IncludeFunctions [DscProperty()] [string[]]$ExcludeFunctions [DscProperty()] [string[]]$IncludeTags [DscProperty()] [string[]]$ExcludeTags [DscProperty()] [int]$MinLevel [DscProperty()] [int]$MaxLevel #endregion Common Logging Settings #region DSC Properties [DscProperty(NotConfigurable)] [Reason[]] $Reasons # Reserved for Azure Guest Configuration #endregion DSC Properties hidden [hashtable] $PropertyMap = @{ LogName = { $false } Source = { $false } UseFallback = { $args[0] } Category = { 1 -gt $args[0] } InfoID = { 1 -gt $args[0] } WarningID = { 1 -gt $args[0] } ErrorID = { 1 -gt $args[0] } ErrorTag = { -not $args[0] } TimeFormat = { -not $args[0] } NumericTagAsID = { -not $args[0] } } [void]Set() { $this.AssertConfig() # Apply Desired State $logHelper = [PsfLoggingHelper]::new($this, 'Eventlog') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Eventlog.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { $extHelper.Clear() $logHelper.Clear() return } $extHelper.Set() $logHelper.Set() } [PSFrameworkLogEventLog]Get() { # Return current actual state $result = [PSFrameworkLogEventLog]::new() $result.InstanceName = $this.InstanceName $result.Ensure = 'Absent' $logHelper = [PsfLoggingHelper]::new($this, 'Eventlog') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Eventlog.$($this.InstanceName)", $this.PropertyMap) foreach ($pair in $logHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } foreach ($pair in $extHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } return $result } [bool]Test() { $this.AssertConfig() $logHelper = [PsfLoggingHelper]::new($this, 'Eventlog') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Eventlog.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { if ($logHelper.Get().Count -gt 0) { return $false } if ($extHelper.Get().Count -gt 0) { return $false } return $true } # Test Common Provider Settings if (-not $logHelper.Test()) { return $false } if (-not $extHelper.Test()) { return $false } return $true } [PsfLoggingHelper]GetLogHelper() { return [PsfLoggingHelper]::new($this, 'Eventlog') } [ExtendedConfigHelper]GetCfgHelper() { return [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Eventlog.$($this.InstanceName)", $this.PropertyMap) } [void]AssertConfig() { if ($this.Ensure -eq 'Absent') { return } if (-not $this.LogName) { throw "Missing Setting: LogName!" } if (-not $this.Source) { throw "Missing Setting: Source!" } } } [DscResource()] class PSFrameworkLogFile { [DscProperty(Mandatory)] [Ensure]$Ensure #region Logging Provider Settings [DscProperty(Key)] [string]$InstanceName [DscProperty()] [string]$CsvDelimiter = ',' [DscProperty()] [string]$FilePath [DscProperty()] [string]$FileType = 'csv' [DscProperty()] [string[]]$Headers = @('ComputerName', 'File', 'FunctionName', 'Level', 'Line', 'Message', 'ModuleName', 'Runspace', 'Tags', 'TargetObject', 'Timestamp', 'Type', 'Username') [DscProperty()] [bool]$IncludeHeader = $true [DscProperty()] [string]$Logname [DscProperty()] [string]$TimeFormat = "yyyy-MM-dd HH:mm:ss.fff" [DscProperty()] [string]$Encoding = 'UTF8' [DscProperty()] [bool]$UTC [DscProperty()] [string]$LogRotatePath [DscProperty()] [string]$LogRetentionTime [DscProperty()] [string]$LogRotateFilter [DscProperty()] [bool]$LogRotateRecurse [DscProperty()] [string]$MutexName [DscProperty()] [bool]$JsonCompress [DscProperty()] [bool]$JsonString [DscProperty()] [bool]$JsonNoComma [DscProperty()] [string]$MoveOnFinal [DscProperty()] [string]$CopyOnFinal #endregion Logging Provider Settings #region Common Logging Settings [DscProperty()] [bool]$Enabled = $true [DscProperty()] [string[]]$IncludeModules [DscProperty()] [string[]]$ExcludeModules [DscProperty()] [string[]]$IncludeFunctions [DscProperty()] [string[]]$ExcludeFunctions [DscProperty()] [string[]]$IncludeTags [DscProperty()] [string[]]$ExcludeTags [DscProperty()] [int]$MinLevel [DscProperty()] [int]$MaxLevel #endregion Common Logging Settings #region DSC Properties [DscProperty(NotConfigurable)] [Reason[]] $Reasons # Reserved for Azure Guest Configuration #endregion DSC Properties hidden [hashtable] $PropertyMap = @{ CsvDelimiter = { -not $args[0] } FilePath = { $false } FileType = { -not $args[0] } Headers = { -not $args[0] } IncludeHeader = { $false } Logname = { -not $args[0] } TimeFormat = { -not $args[0] } Encoding = { -not $args[0] } UTC = { -not $args[0] } LogRotatePath = { -not $args[0] } LogRetentionTime = { -not $args[0] } LogRotateFilter = { -not $args[0] } LogRotateRecurse = { -not $args[0] } MutexName = { -not $args[0] } JsonCompress = { -not $args[0] } JsonString = { -not $args[0] } JsonNoComma = { -not $args[0] } MoveOnFinal = { -not $args[0] } CopyOnFinal = { -not $args[0] } } [void]Set() { $this.AssertConfig() # Apply Desired State $logHelper = [PsfLoggingHelper]::new($this, 'Logfile') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Logfile.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { $extHelper.Clear() $logHelper.Clear() return } $extHelper.Set() $logHelper.Set() } [PSFrameworkLogfile]Get() { # Return current actual state $result = [PSFrameworkLogfile]::new() $result.InstanceName = $this.InstanceName $result.Ensure = 'Absent' $logHelper = [PsfLoggingHelper]::new($this, 'Logfile') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Logfile.$($this.InstanceName)", $this.PropertyMap) foreach ($pair in $logHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } foreach ($pair in $extHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } return $result } [bool]Test() { $this.AssertConfig() $logHelper = [PsfLoggingHelper]::new($this, 'Logfile') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Logfile.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { if ($logHelper.Get().Count -gt 0) { return $false } if ($extHelper.Get().Count -gt 0) { return $false } return $true } # Test Common Provider Settings if (-not $logHelper.Test()) { return $false } if (-not $extHelper.Test()) { return $false } return $true } [PsfLoggingHelper]GetLogHelper() { return [PsfLoggingHelper]::new($this, 'Logfile') } [ExtendedConfigHelper]GetCfgHelper() { return [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Logfile.$($this.InstanceName)", $this.PropertyMap) } [void]AssertConfig() { if ($this.Ensure -eq 'Absent') { return } if (-not $this.FilePath) { throw "Missing Setting: FilePath!" } } } [DscResource()] class PSFrameworkLogSql { #region DSC Properties [DscProperty(Mandatory)] [Ensure]$Ensure [DscProperty(Key)] [string]$InstanceName [DscProperty()] [string]$SqlServer [DscProperty()] [string]$Database [DscProperty()] [string]$Schema [DscProperty()] [string]$Table [DscProperty()] [PSCredential]$Credential [DscProperty()] [string[]]$Headers #region Common Logging Settings [DscProperty()] [bool]$Enabled = $true [DscProperty()] [string[]]$IncludeModules [DscProperty()] [string[]]$ExcludeModules [DscProperty()] [string[]]$IncludeFunctions [DscProperty()] [string[]]$ExcludeFunctions [DscProperty()] [string[]]$IncludeTags [DscProperty()] [string[]]$ExcludeTags [DscProperty()] [int]$MinLevel [DscProperty()] [int]$MaxLevel #endregion Common Logging Settings [DscProperty(NotConfigurable)] [Reason[]] $Reasons # Reserved for Azure Guest Configuration #endregion DSC Properties hidden [hashtable] $PropertyMap = @{ SqlServer = { $false } Database = { $false } Schema = { $false } Table = { $false } Credential = { $null -eq $args[0] } Headers = { $null -eq $args[0] } } [void]Set() { $this.AssertConfig() # Apply Desired State $logHelper = [PsfLoggingHelper]::new($this, 'Sql') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Sql.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { $extHelper.Clear() $logHelper.Clear() return } $extHelper.Set() $logHelper.Set() } [PSFrameworkLogSql]Get() { # Return current actual state $result = [PSFrameworkLogSql]::new() $result.InstanceName = $this.InstanceName $result.Ensure = 'Absent' $logHelper = [PsfLoggingHelper]::new($this, 'Sql') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Sql.$($this.InstanceName)", $this.PropertyMap) foreach ($pair in $logHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } foreach ($pair in $extHelper.Get().GetEnumerator()) { $result.Ensure = 'Present' $result.$($pair.Key) = $pair.Value } return $result } [bool]Test() { $this.AssertConfig() $logHelper = [PsfLoggingHelper]::new($this, 'Sql') $extHelper = [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Sql.$($this.InstanceName)", $this.PropertyMap) if ($this.Ensure -eq 'Absent') { if ($logHelper.Get().Count -gt 0) { return $false } if ($extHelper.Get().Count -gt 0) { return $false } return $true } # Test Common Provider Settings if (-not $logHelper.Test()) { return $false } if (-not $extHelper.Test()) { return $false } return $true } [PsfLoggingHelper]GetLogHelper() { return [PsfLoggingHelper]::new($this, 'Sql') } [ExtendedConfigHelper]GetCfgHelper() { return [ExtendedConfigHelper]::New($this, 'SystemDefault', "PSFramework.Logging.Sql.$($this.InstanceName)", $this.PropertyMap) } [void]AssertConfig() { if ($this.Ensure -eq 'Absent') { return } if (-not $this.SqlServer) { throw "Missing Setting: SqlServer!" } if (-not $this.Database) { throw "Missing Setting: Database!" } if (-not $this.Schema) { throw "Missing Setting: Schema!" } if (-not $this.Table) { throw "Missing Setting: Table!" } } } function Get-PSFrameworkConfig { <# .SYNOPSIS Returns the current state of the configuration setting. .DESCRIPTION Returns the current state of the configuration setting. .PARAMETER FullName The full name of the PSFramework configuration setting. .PARAMETER Ensure Whether it should be present or absent. .PARAMETER ConfigScope What scope it should be applied to. .PARAMETER Value The value it should have. .PARAMETER ValueType The type of the value that should be defined. Used because DSC does not allow Object types, thus requiring conversion afterwards. .EXAMPLE PS C:\> Get-PSFramework @param Returns the current state of the configuration setting. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FullName, [Parameter(Mandatory = $true)] [Ensure] $Ensure, [Scope] $ConfigScope, [string] $Value, [ConfigType] $ValueType ) process { $result = [PSFrameworkConfig]::new() $result.ConfigScope = $ConfigScope $result.FullName = $FullName if ($ConfigScope -eq 'SystemDefault') { $path = $script:config.PathDefault } else { $path = $script:config.PathEnforced } $result.Ensure = 'Absent' $item = Get-ItemProperty -Path $path -Name $FullName -ErrorAction Ignore if ($item -and $item.PSObject.Properties.Name -contains $FullName) { $result.Ensure = 'Present' $result.Value = $item.$FullName } $result } } function Set-PSFrameworkConfig { <# .SYNOPSIS Applies the desired value for the PSFramework configuration setting. .DESCRIPTION Applies the desired value for the PSFramework configuration setting. .PARAMETER FullName The full name of the PSFramework configuration setting. .PARAMETER Ensure Whether it should be present or absent. .PARAMETER ConfigScope What scope it should be applied to. .PARAMETER Value The value it should have. .PARAMETER ValueType The type of the value that should be defined. Used because DSC does not allow Object types, thus requiring conversion afterwards. .EXAMPLE PS C:\> Set-PSFrameworkConfig @param Applies the desired value for the PSFramework configuration setting. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FullName, [Parameter(Mandatory = $true)] [Ensure] $Ensure, [Scope] $ConfigScope, [string] $Value, [ConfigType] $ValueType ) process { if ($ConfigScope -eq 'SystemDefault') { $path = $script:config.PathDefault } else { $path = $script:config.PathEnforced } if ($Ensure -eq 'Absent') { if (-not (Test-Path -Path $path)) { return } $current = Get-ItemProperty -Path $path if ($current.PSObject.Properties.Name -notcontains $FullName) { return } # Ensure the casing is exact, just in case $name = $current.PSObject.Properties.Name | Where-Object { $_ -eq $FullName } Remove-ItemProperty -Path $path -Name $name -ErrorAction Stop return } try { $intendedValue = [PSFrameworkConfig]::Convert($Value, $ValueType) } catch { throw "Error setting $FullName : Failed to convert $Value to $ValueType! $_" } if ($ValueType -ne 'PsfConfig' -and -not [PSFrameworkConfig]::IsLegalType($intendedValue)) { if ($null -eq $intendedValue) { $convertedValueType = '<null>' } else { $convertedValueType = $intendedValue.GetType() } throw "Datatype not supported: $($convertedValueType)" } if (-not (Test-Path $path)) { $null = New-Item $Path -Force } $registryValue = $intendedValue if ($ValueType -ne 'PsfConfig') { $registryValue = [PSFrameworkConfig]::Serialize($intendedValue) } Set-ItemProperty -Path $path -Name $FullName -Value $registryValue -ErrorAction Stop } } function Test-PSFrameworkConfig { <# .SYNOPSIS Tests, whether the desired value/state for the PSFramework configuration setting applies. .DESCRIPTION Tests, whether the desired value/state for the PSFramework configuration setting applies. .PARAMETER FullName The full name of the PSFramework configuration setting. .PARAMETER Ensure Whether it should be present or absent. .PARAMETER ConfigScope What scope it should be applied to. .PARAMETER Value The value it should have. .PARAMETER ValueType The type of the value that should be defined. Used because DSC does not allow Object types, thus requiring conversion afterwards. .PARAMETER Current The object representing the current state. .EXAMPLE PS C:\> Test-PSFrameworkConfig @param Tests, whether the desired value/state for the PSFramework configuration setting applies. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FullName, [Parameter(Mandatory = $true)] [Ensure] $Ensure, [Scope] $ConfigScope, [string] $Value, [ConfigType] $ValueType, [PSFrameworkConfig] $Current ) process { if ($Current.Ensure -ne $Ensure) { return $false } if ($Ensure -eq 'Absent') { return $true } try { $intendedValue = [PSFrameworkConfig]::Convert($Value, $ValueType) } catch { throw "Error testing $FullName : Failed to convert $Value to $ValueType! $_" } $registryValue = $intendedValue if ($ValueType -ne 'PsfConfig') { $registryValue = [PSFrameworkConfig]::Serialize($intendedValue) } $registryValue -eq $Current.Value } } $script:config = @{ PathDefault = "HKLM:\SOFTWARE\Microsoft\WindowsPowerShell\PSFramework\Config\Default" PathEnforced = "HKLM:\SOFTWARE\Microsoft\WindowsPowerShell\PSFramework\Config\Enforced" } |