Module/STIG/Convert/Functions.PowerStigXml.ps1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. #region Main Function <# .SYNOPSIS Identifies the type of STIG that has been input and selects the proper private function to further convert the STIG strings into usable objects. .DESCRIPTION This function enables the core translation of the raw xccdf file by reading the benchmark title property to determine where to send the data for processing. When a ruleset match is found, the xccdf data is sent to private functions that are dedicated to processing individual STIG setting types, such as registry settings or security policy. If the function is unable to find a rule set match, an error is returned. .PARAMETER Path The path to the xccdf file to be processed. .PARAMETER IncludeRawString This will add the 'Check-Content' from the xcccdf to the output for any additional validation or spot checking that may be needed. .PARAMETER RuleIdFilter Filters the list rules that are converted to simplify debugging the conversion process. .EXAMPLE ConvertFrom-StigXccdf -Path C:\Stig\U_Windows_2012_and_2012_R2_MS_STIG_V2R8_Manual-xccdf.xml .OUTPUTS Custom objects are created from the STIG base class that are provided in the module .NOTES This is an ongoing project that should be retested with each iteration of the STIG. This is due to the non-standard way, the content is published. Each version of the STIG may require a rule to be updated to account for a new string format. All the formatting rules are heavily tested, so making changes is a simple task. .LINK http://iase.disa.mil/stigs/Lists/stigs-masterlist/AllItems.aspx #> function ConvertFrom-StigXccdf { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter()] [string[]] $RuleIdFilter ) # Get the xml data from the file path provided. $stigBenchmarkXml = Get-StigXccdfBenchmarkContent -Path $path # Global variable needed to distinguish between the IIS server and site stigs. Server Stig needs xIISLogging resource, Site Stig needs xWebsite $global:stigTitle = $stigBenchmarkXml.title # Global variable needed to set and get specific logic needed for filtering and parsing FileContentRules switch ($true) { {$global:stigXccdfName -and -join ((Split-Path -Path $path -Leaf).Split('_') | Select-Object -Index (1, 2)) -eq ''} { break; } {!$global:stigXccdfName -or $global:stigXccdfName -ne -join ((Split-Path -Path $path -Leaf).Split('_') | Select-Object -Index (1, 2))} { $global:stigXccdfName = -join ((Split-Path -Path $path -Leaf).Split('_') | Select-Object -Index (1, 2)) break; } } # Read in the root stig data from the xml additional functions will dig in deeper $stigRuleParams = @{ StigGroupListChangeLog = Get-RuleChangeLog -Path $Path } if ($RuleIdFilter) { $stigRuleParams.StigGroupList = $stigBenchmarkXml.Group | Where-Object {$RuleIdFilter -contains $PSItem.Id} } else { $stigRuleParams.StigGroupList = $stigBenchmarkXml.Group } # The benchmark title drives the rest of the function and must exist to continue. if ( $null -eq $stigBenchmarkXml.title ) { Write-Error -Message 'The Benchmark title property is null. Unable to determine ruleset target.' return } Get-RegistryRuleExpressions -Path $Path -StigBenchmarkXml $stigBenchmarkXml return Get-StigRuleList @stigRuleParams } <# .SYNOPSIS Loads the regular expressions files .DESCRIPTION This function loads the regular expression sets to process registry rules in the xccdf file. .PARAMETER Path The path to the xccdf file to be processed. .PARAMETER StigBenchmarkXml The xml for the xccdf file to be processed. #> function Get-RegistryRuleExpressions { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [object] $StigBenchmarkXml ) begin { # Use $stigBenchmarkXml.id to determine the stig file $benchmarkId = Split-BenchmarkId -Id $StigBenchmarkXml.id -FilePath $Path if ([string]::IsNullOrEmpty($benchmarkId.TechnologyRole)) { $benchmarkId.TechnologyRole = $StigBenchmarkXml.id } # Handles testing and production $xccdfFileName = Split-Path $Path -Leaf $spInclude = @('Data.Core.ps1') if ($xccdfFileName -eq 'TextData.xml') { # Query TechnologyRole and map to file $officeApps = @('Outlook', 'Excel', 'PowerPoint', 'Word') $mcafeeApps = @('VirusScan') $spExclude = @($MyInvocation.MyCommand.Name, 'Template.*.txt', 'Data.ps1', 'Functions.*.ps1', 'Methods.ps1') switch ($benchmarkId.TechnologyRole) { { $null -ne ($officeApps | Where-Object { $benchmarkId.TechnologyRole -match $_ }) } { $spInclude += "Data.Office.ps1" } { $null -ne ($mcafeeApps | Where-Object { $benchmarkId.TechnologyRole -match $_ }) } { $spInclude += "Data.Mcafee.ps1" } } } else { # Query directory of xccdf file $spResult = Split-Path (Split-Path $Path -Parent) -Leaf if ($spResult) { $spInclude += "Data." + $spResult + ".ps1" } } } process { # Load specific and core expression sets $childItemParams = @{ Path = "$PSScriptRoot\..\..\Rule\Convert" Exclude = $spExclude Include = $spInclude Recurse = $true } $spSupportFileList = Get-ChildItem @childItemParams | Sort-Object -Descending Clear-Variable SingleLine* -Scope Global foreach ($supportFile in $spSupportFileList) { Write-Verbose "Loading $($supportFile.FullName)" . $supportFile.FullName } } } <# .SYNOPSIS This function generates a new xml file based on the convert objects from ConvertFrom-StigXccdf. .PARAMETER Path The full path to the xccdf to convert. .PARAMETER Destination The full path to save the converted xml to. .PARAMETER CreateOrgSettingsFile Creates the orginazational settings files associated with the version of the STIG. .PARAMETER DoNotExportRawString Excludes the check-content elemet content from the converted object. .PARAMETER RuleIdFilter Filters the list rules that are converted to simplify debugging the conversion process. .PARAMETER DoNotExportDescription Excludes the Description elemet content from the converted object. #> function ConvertTo-PowerStigXml { [CmdletBinding()] [OutputType([xml])] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter()] [string] $Destination, [Parameter()] [switch] $CreateOrgSettingsFile, [Parameter()] [switch] $DoNotExportRawString, [Parameter()] [string[]] $RuleIdFilter, [Parameter()] [switch] $DoNotExportDescription ) begin { $CurrentVerbosePreference = $global:VerbosePreference if ($PSBoundParameters.ContainsKey('Verbose')) { $global:VerbosePreference = 'Continue' } } process { $convertedStigObjects = ConvertFrom-StigXccdf -Path $Path -RuleIdFilter $RuleIdFilter # Get the raw xccdf xml to pull additional details from the root node. [xml] $xccdfXml = Get-Content -Path $Path -Encoding UTF8 [version] $stigVersionNumber = Get-StigVersionNumber -StigDetails $xccdfXml $ruleTypeList = Get-RuleTypeList -StigSettings $convertedStigObjects # Start the XML doc and add the root element $xmlDocument = [System.XML.XMLDocument]::New() [System.XML.XMLElement] $xmlRoot = $xmlDocument.CreateElement( 'DISASTIG' ) <# Append as child to an existing node. This method will 'leak' an object out of the function so DO NOT remove the [void] #> [void] $xmlDocument.appendChild( $xmlRoot ) $xmlRoot.SetAttribute( 'version' , $xccdfXml.Benchmark.version ) $xmlRoot.SetAttribute( 'classification', 'UNCLASSIFIED' ) $xmlRoot.SetAttribute( 'customname' , '' ) $xmlRoot.SetAttribute( 'stigid' , $xccdfXml.Benchmark.ID ) $xmlRoot.SetAttribute( 'description' , $xccdfXml.Benchmark.description ) $xmlRoot.SetAttribute( 'filename' , (Split-Path -Path $Path -Leaf) ) $xmlRoot.SetAttribute( 'releaseinfo' , $xccdfXml.Benchmark.'plain-text'.InnerText ) $xmlRoot.SetAttribute( 'title' , $xccdfXml.Benchmark.title ) $xmlRoot.SetAttribute( 'notice' , $xccdfXml.Benchmark.notice.Id ) $xmlRoot.SetAttribute( 'source' , $xccdfXml.Benchmark.reference.source ) $xmlRoot.SetAttribute( 'fullversion', $stigVersionNumber ) $xmlRoot.SetAttribute( 'created', $(Get-Date).ToShortDateString() ) # Add the STIG types as child elements foreach ( $ruleType in $ruleTypeList ) { # Create the rule type node [System.XML.XMLElement] $xmlRuleType = $xmlDocument.CreateElement( $ruleType ) # Append as child to an existing node. DO NOT remove the [void] [void] $xmlRoot.appendChild( $xmlRuleType ) $XmlRuleType.SetAttribute( $xmlattribute.ruleDscResourceModule, $dscResourceModule.$ruleType ) # Get the rules for the current STIG type. $rules = $convertedStigObjects | Where-Object { $PSItem.GetType().ToString() -eq $ruleType } # Get the list of properties of the current object type to use as child elements [System.Collections.ArrayList] $properties = $rules | Get-Member | Where-Object MemberType -eq Property | Select-Object Name -ExpandProperty Name <# The $properties array is used to set the child elements of the rule. Remove the base class properties from the array list that we do not want added as child elements. #> $propertiesToRemove = @($xmlattribute.ruleId, $xmlattribute.ruleSeverity, $xmlattribute.ruleConversionStatus, $xmlattribute.ruleTitle, $xmlattribute.ruleDscResource) <# Because the Remove method on an array is case sensitive and the properties names in $propertiesToRemove are in different case from $properties we use the -in comparison operator to filter and return the proper case #> $propertiesToRemove = $properties | Where-Object -FilterScript {$PSItem -in $propertiesToRemove} ### [TODO] ### <# Remove the Description if explicited requested. Once all PowerSTIG data files are updated with the description attribute, this and the $DoNotExportDescription can be removed from the function. This field is used to automatically generate a populated STIG checklist. #> if ( $DoNotExportDescription ) { $propertiesToRemove += 'Description' } ### END TODO ### # Remove the raw string from the output if it was not requested. if ( $DoNotExportRawString ) { $propertiesToRemove += 'RawString' } # These properties are removed becasue they are attributes of the object, not elements foreach ( $propertyToRemove in $propertiesToRemove ) { [void] $properties.Remove( $propertyToRemove ) } # Add the STIG details to the xml document. foreach ( $rule in $rules ) { [System.XML.XMLElement] $xmlRuleTypeProperty = $xmlDocument.CreateElement( 'Rule' ) # Append as child to an existing node. DO NOT remove the [void] [void] $xmlRuleType.appendChild( $xmlRuleTypeProperty ) # Set the base class properties $xmlRuleTypeProperty.SetAttribute( $xmlattribute.ruleId, $rule.ID ) $xmlRuleTypeProperty.SetAttribute( $xmlattribute.ruleSeverity, $rule.severity ) $xmlRuleTypeProperty.SetAttribute( $xmlattribute.ruleConversionStatus, $rule.conversionstatus ) $xmlRuleTypeProperty.SetAttribute( $xmlattribute.ruleTitle, $rule.title ) $xmlRuleTypeProperty.SetAttribute( $xmlattribute.ruleDscResource, $rule.dscresource ) foreach ( $property in $properties ) { [System.XML.XMLElement] $xmlRuleTypePropertyUnique = $xmlDocument.CreateElement( $property ) # Append as child to an existing node. DO NOT remove the [void] [void] $xmlRuleTypeProperty.appendChild( $xmlRuleTypePropertyUnique ) # Skip any blank vaules if ($null -eq $rule.$property) { continue } <# The Permission rule returns an ACE list that needs to be serialized on a second level. This will pick that up and expand the object in the xml. #> if ($property -eq 'AccessControlEntry') { foreach ($ace in $rule.$property) { [System.XML.XMLElement] $aceEntry = $xmlDocument.CreateElement( 'Entry' ) [void] $xmlRuleTypePropertyUnique.appendChild( $aceEntry ) # Add the ace entry Type [System.XML.XMLElement] $aceEntryType = $xmlDocument.CreateElement( 'Type' ) [void] $aceEntry.appendChild( $aceEntryType ) $aceEntryType.InnerText = $ace.Type # Add the ace entry Principal [System.XML.XMLElement] $aceEntryPrincipal = $xmlDocument.CreateElement( 'Principal' ) [void] $aceEntry.appendChild( $aceEntryPrincipal ) $aceEntryPrincipal.InnerText = $ace.Principal # Add the ace entry Principal [System.XML.XMLElement] $aceEntryForcePrincipal = $xmlDocument.CreateElement( 'ForcePrincipal' ) [void] $aceEntry.appendChild( $aceEntryForcePrincipal ) $aceEntryForcePrincipal.InnerText = $ace.ForcePrincipal # Add the ace entry Inheritance flag [System.XML.XMLElement] $aceEntryInheritance = $xmlDocument.CreateElement( 'Inheritance' ) [void] $aceEntry.appendChild( $aceEntryInheritance ) $aceEntryInheritance.InnerText = $ace.Inheritance # Add the ace entery FileSystemRights [System.XML.XMLElement] $aceEntryRights = $xmlDocument.CreateElement( 'Rights' ) [void] $aceEntry.appendChild( $aceEntryRights ) $aceEntryRights.InnerText = $ace.Rights } } elseif ($property -eq 'LogCustomFieldEntry') { foreach ($entry in $rule.$property) { [System.XML.XMLElement] $logCustomFieldEntry = $xmlDocument.CreateElement( 'Entry' ) [void] $xmlRuleTypePropertyUnique.appendChild( $logCustomFieldEntry ) [System.XML.XMLElement] $entrySourceType = $xmlDocument.CreateElement( 'SourceType' ) [void] $logCustomFieldEntry.appendChild( $entrySourceType ) $entrySourceType.InnerText = $entry.SourceType [System.XML.XMLElement] $entrySourceName = $xmlDocument.CreateElement( 'SourceName' ) [void] $logCustomFieldEntry.appendChild( $entrySourceName ) $entrySourceName.InnerText = $entry.SourceName } } else { $xmlRuleTypePropertyUnique.InnerText = $rule.$property } } } } $fileList = Get-PowerStigFileList -StigDetails $xccdfXml -Destination $Destination -Path $Path try { $xmlDocument.save($fileList.Settings.FullName) # The save method does not add the required blank line to the file Write-Output -InputObject "`r`n" | Out-File -FilePath $fileList.Settings.FullName -Append -Encoding utf8 -NoNewline Write-Output "Converted Output: $($fileList.Settings.FullName)" } catch [System.Exception] { Write-Error -Message $error[0] } if ($CreateOrgSettingsFile) { $OrganizationalSettingsXmlFileParameters = @{ 'convertedStigObjects' = $convertedStigObjects 'StigVersionNumber' = $stigVersionNumber 'Destination' = $fileList.OrgSettings.FullName } New-OrganizationalSettingsXmlFile @OrganizationalSettingsXmlFileParameters Write-Output "Org Settings Output: $($fileList.OrgSettings.FullName)" } } end { $global:VerbosePreference = $CurrentVerbosePreference } } <# .SYNOPSIS Compares the converted xml files from ConvertFrom-StigXccdf. .PARAMETER OldStigPath The full path to the previous PowerStigXml file to convert. .PARAMETER NewStigPath The full path to the current PowerStigXml file to convert. #> function Compare-PowerStigXml { [CmdletBinding()] [OutputType([psobject])] param ( [Parameter(Mandatory = $true)] [string] $OldStigPath, [Parameter(Mandatory = $true)] [string] $NewStigPath, [Parameter()] [switch] $IgnoreRawString ) begin { $CurrentVerbosePreference = $global:VerbosePreference if ($PSBoundParameters.ContainsKey('Verbose')) { $global:VerbosePreference = 'Continue' } } process { [xml] $OldStigContent = Get-Content -Path $OldStigPath -Encoding UTF8 [xml] $NewStigContent = Get-Content -Path $NewStigPath -Encoding UTF8 $rules = $OldStigContent.DISASTIG.ChildNodes.ToString() -split "\s" $returnCompareList = @{} $compareObjects = @() $propsToIgnore = @() if ($ignoreRawString) { $propsToIgnore += "rawString" } foreach ( $rule in $rules ) { $OldStigXml = Select-Xml -Xml $OldStigContent -XPath "//$rule/*" $NewStigXml = Select-Xml -Xml $NewStigContent -XPath "//$rule/*" if ($OldStigXml.Count -lt 2) { $prop = (Get-Member -MemberType Properties -InputObject $OldStigXml.Node).Name } else { $prop = (Get-Member -MemberType Properties -InputObject $OldStigXml.Node[0]).Name } $OldStigXml = $OldStigXml.Node | Select-Object $prop -ExcludeProperty $propsToIgnore if ($NewStigXml.Count -lt 2) { $prop = (Get-Member -MemberType Properties -InputObject $NewStigXml.Node).Name } else { $prop = (Get-Member -MemberType Properties -InputObject $NewStigXml.Node[0]).Name } $NewStigXml = $NewStigXml.Node | Select-Object $prop -ExcludeProperty $propsToIgnore $compareObjects += Compare-Object -ReferenceObject $OldStigXml -DifferenceObject $NewStigXml -Property $prop } $compareIdList = $compareObjects.Id foreach ($stig in $compareObjects) { $compareIdListFilter = $compareIdList | Where-Object {$PSitem -eq $stig.Id} if ($compareIdListFilter.Count -gt "1") { $delta = "changed" } else { if ($stig.SideIndicator -eq "=>") { $delta = "added" } elseif ($stig.SideIndicator -eq "<=") { $delta = "deleted" } } if ( -not $returnCompareList.ContainsKey($stig.Id)) { [void] $returnCompareList.Add($stig.Id, $delta) } } $returnCompareList.GetEnumerator() | Sort-Object Name } end { $global:VerbosePreference = $CurrentVerbosePreference } } #endregion #region Private Functions $organizationalSettingRootComment = @' The organizational settings file is used to define the local organizations preferred setting within an allowed range of the STIG. Each setting in this file is linked by STIG ID and the valid range is in an associated comment. '@ <# .SYNOPSIS Creates the Organizational settings file that accompanies the converted STIG data. .PARAMETER convertedStigObjects The Converted Stig Objects to sort through .PARAMETER StigVersionNumber The version number of the xccdf that is being processed. .PARAMETER Destination The path to store the output file. #> function New-OrganizationalSettingsXmlFile { [CmdletBinding()] [OutputType()] param ( [Parameter(Mandatory = $true)] [psobject] $ConvertedStigObjects, [Parameter(Mandatory = $true)] [version] $StigVersionNumber, [Parameter(Mandatory = $true)] [string] $Destination ) $OrgSettings = Get-StigObjectsWithOrgSettings -ConvertedStigObjects $ConvertedStigObjects $xmlDocument = [System.XML.XMLDocument]::New() ######################################### Root object ########################################## [System.XML.XMLElement] $xmlRootElement = $xmlDocument.CreateElement('OrganizationalSettings') [void] $xmlDocument.appendChild($xmlRootElement) [void] $xmlRootElement.SetAttribute('fullversion', $StigVersionNumber) $rootComment = $xmlDocument.CreateComment($organizationalSettingRootComment) [void] $xmlDocument.InsertBefore($rootComment, $xmlRootElement) ######################################### Root object ########################################## ######################################### ID object ########################################## foreach ($orgSetting in $OrgSettings) { $orgSettingProperty = Get-OrgSettingPropertyFromStigRule -ConvertedStig $orgSetting [System.XML.XMLElement] $xmlSettingChildElement = $xmlDocument.CreateElement('OrganizationalSetting') [void] $xmlRootElement.appendChild($xmlSettingChildElement) $xmlSettingChildElement.SetAttribute($xmlAttribute.ruleId , $orgSetting.id) foreach ($property in $orgSettingProperty) { $xmlAttribute.Add($property, $property) $xmlSettingChildElement.SetAttribute($xmlAttribute.$property , [string]::Empty) $xmlAttribute.Remove($property) } $settingComment = " Ensure $(($orgSetting.OrganizationValueTestString) -f "'$($orgSetting.Id)'")" $rangeNameComment = $xmlDocument.CreateComment($settingComment) [void] $xmlRootElement.InsertBefore($rangeNameComment, $xmlSettingChildElement) } ######################################### ID object ########################################## $xmlDocument.Save($Destination) Write-Output -InputObject "`r`n" | Out-File -FilePath $Destination -Append -Encoding utf8 -NoNewline } <# .SYNOPSIS Filters the list of STIG objects and returns anything that requires an organizational decision. .PARAMETER convertedStigObjects A reference to the object that contains the converted stig data. .NOTES This function should only be called from the public ConvertTo-DscStigXml function. #> function Get-StigObjectsWithOrgSettings { [CmdletBinding()] [OutputType([psobject])] param ( [Parameter(Mandatory = $true)] [psobject] $ConvertedStigObjects ) $ConvertedStigObjects | Where-Object { $PSitem.OrganizationValueRequired -eq $true} } function Get-OrgSettingPropertyFromStigRule { [CmdletBinding()] [OutputType([string[]])] param ( [Parameter(Mandatory = $true)] [psobject] $ConvertedStig ) $propertiesToRemove = Get-BaseRulePropertyName [System.Collections.ArrayList] $rulePropertyNames = (Get-Member -InputObject $ConvertedStig -MemberType Property).Name foreach ($property in $propertiesToRemove) { $rulePropertyNames.RemoveAt($rulePropertyNames.IndexOf($property)) } foreach ($propertyName in $rulePropertyNames) { if ([string]::IsNullOrEmpty($ConvertedStig.$propertyName)) { [array] $orgSettingProperties += $propertyName } } return $orgSettingProperties } <# .SYNOPSIS Creates HardCodedRule log file entry example .DESCRIPTION Queries a specific RuleType and generates an example log file entry for HardCodedRules in PowerSTIG. .PARAMETER RuleId The STIG RuleId that should be included with the HardCodedRule log file example. .PARAMETER RuleType The RuleType(s) that should be used when creating a HardCodedRule log file entry. .EXAMPLE Get-HardCodedRuleLogFileEntry -RuleId V-1000 -RuleType WindowsFeatureRule Outputs the following single HardCodedRule log entry example: V-1000::*::HardCodedRule(WindowsFeatureRule)@{DscResource = 'WindowsFeature'; Ensure = $null; Name = $null} .EXAMPLE Get-HardCodedRuleLogFileEntry -RuleId V-1000 -RuleType WindowsFeatureRule, FileContentRule Outputs the following split HardCodedRule log entry example: V-1000::*::HardCodedRule(WindowsFeatureRule)@{DscResource = 'WindowsFeature'; Ensure = $null; Name = $null}<splitRule>HardCodedRule(FileContentRule)@{DscResource = 'ReplaceText'; Key = $null; Value = $null} #> function Get-HardCodedRuleLogFileEntry { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [String] $RuleId ) DynamicParam { Get-DynamicParameterRuleTypeName } begin { # Bind the specified parameter values to RuleType var $RuleType = $PSBoundParameters['RuleType'] $counter = 0 # Dynamically query the base rule common properties to remove $commonPropertiesToRemove = Get-BaseRulePropertyName # Log file patterns to build log file string $logFileRuleId = '{0}::*::' -f $RuleId $logFileHardCodedRulePattern = "{0}HardCodedRule({1}){4}DscResource = '{2}'{3}{5}" $keyValuePairPattern = '; {0} = $null' $splitRulePattern = '<splitRule>' $open, $close = '@{', '}' } process { $returnString = foreach ($type in $RuleType) { # Create convert rule of the given type in order to obtain rule specific properties $ruleTypeConvert = New-Object -TypeName ("$type`Convert") $ruleTypeConvert.SetDscResource() $ruleTypeDscResource = $ruleTypeConvert.DscResource # Query all valid non-base rule property names $ruleProperties = (Get-Member -InputObject $ruleTypeConvert -MemberType Property).Name | Where-Object -FilterScript {$PSItem -notin $commonPropertiesToRemove} # Build a string for DSC Resource specific parameters, without values $keyValuePair = @() foreach ($dscKey in $ruleProperties) { $keyValuePair += $keyValuePairPattern -f $dscKey } $keyValuePair = -join $keyValuePair # First time through, add the rule id, second and more will add the split delimiter if ($counter -eq 0) { $logFileHardCodedRulePattern -f $logFileRuleId, $type, $ruleTypeDscResource, $keyValuePair, $open, $close $counter++ } else { $logFileHardCodedRulePattern -f $splitRulePattern, $type, $ruleTypeDscResource, $keyValuePair, $open, $close } } return -join $returnString } } <# .SYNOPSIS Helper function to return the base rule property names. #> function Get-BaseRulePropertyName { [CmdletBinding()] [OutputType([string[]])] param() $baseRule = [Rule]::new() return (Get-Member -InputObject $baseRule -MemberType Property).Name } <# .SYNOPSIS Returns a list of all PowerSTIG RuleTypes. Used to dynamically provide Values to Get-HardCodedRuleLogFileEntry RuleType parameter. #> function Get-DynamicParameterRuleTypeName { [CmdletBinding()] [OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])] param() $parameterName = 'RuleType' $paramAttribute = [System.Management.Automation.ParameterAttribute]::new() $paramAttribute.Mandatory = $true $paramAttribute.Position = 1 $getChildItemParams = @{ Path = "$PSScriptRoot\..\.." File = $true Exclude = 'ManualRule.psm1', 'DocumentRule.psm1' Filter = '*?Rule.psm1' Recurse = $true } [string[]]$validRuleTypes = (Get-ChildItem @getChildItemParams).Name -replace '.psm1' $validateSet = [System.Management.Automation.ValidateSetAttribute]::new($validRuleTypes) $attribCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attribCollection.Add($paramAttribute) $attribCollection.Add($validateSet) $runtimeDefinedParam = [System.Management.Automation.RuntimeDefinedParameter]::new($parameterName, [string[]], $attribCollection) $runtimeDefinedParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $runtimeDefinedParamDictionary.Add($parameterName, $runtimeDefinedParam) return $runtimeDefinedParamDictionary } <# .SYNOPSIS Looks up the change log for a given xccdf file and loads the changes #> function Get-RuleChangeLog { [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory = $true)] [string] $Path ) $path = $Path -replace '\.xml', '.log' try { $updateLog = Get-Content -Path $path -Encoding UTF8 -Raw -ErrorAction Stop } catch { Write-Warning "$path not found. Please create it if needed." return @{} } # regex matches is used to capture the log content directly to the changes variable $changeList = [regex]::Matches( $updateLog, '(?<id>V-\d+)(?:::)(?<oldText>.+)(?:::)(?<newText>.+)' ) # The function returns a hastable $updateList = @{} foreach ($change in $changeList) { $id = $change.Groups.Item('id').value $oldText = $change.Groups.Item('oldText').value # The trim removes any potential CRLF entries that will show up in a regex escape sequence. # The replace replaces `r`n with an actual new line. This is useful if you need to add data on a separate line. $newText = $change.Groups.Item('newText').value.Trim().Replace('`r`n',[Environment]::NewLine) $changeObject = [pscustomobject] @{ OldText = $oldText NewText = $newText } <# Some rule have multiple changes that need to be made, so if a rule already has a change, then add the next change to the value (array) #> if ($updateList.ContainsKey($id)) { $null = $updateList[$id] += $changeObject } else { $null = $updateList.Add($id, @($changeObject)) } } $updateList } #endregion |