SAM.STIGChecklist.psm1

# [Variables] -----------------------------------------------------------------------------------------------------

[String]$SAMRoot = $PSScriptRoot
[String]$cklSchemaPath = [IO.Path]::Combine("$SAMRoot", 'Data', 'U_Checklist_Schema_V2.xsd')
[String]$regexFQDN = "^(?!.*?_.*?)(?!(?:[\w]+?\.)?\-[\w\.\-]*?)(?![\w]+?\-\.(?:[\w\.\-]+?))(?=[\w])(?=[\w\.\-]*?\.+[\w\.\-]*?)(?![\w\.\-]{254})(?!(?:\.?[\w\-\.]*?[\w\-]{64,}\.)+?)[\w\.\-]+?(?<![\w\-\.]*?\.[\d]+?)(?<=[\w\-]{2,})(?<![\w\-]{25})$"
[String]$regexMAC = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})$"

# [Functions] -----------------------------------------------------------------------------------------------------
function ConvertTo-NewChecklistVersion {

    <#
        .SYNOPSIS
            Convert Checklist to a new version.

        .DESCRIPTION
            Convert Checklist to a new version while keeping the orginal values. This function handles searching LegacyIDs as well if you are importing a checklist before their IDs were converted.

        .EXAMPLE
            PS C:\> ConvertTo-NewChecklistVersion -XCCDF "E:\U_MS_DotNet_Framework_4-0_V2R1_STIG.zip" -OrginalChecklist "E:\U_MS_Dot_Net_Framework_4-0_STIG_V1R9-Convert.ckl" -Destination E:\ConvertTest -Force -Verbose

            This example demonstrates how to convert a single checklist the new checklist version.
    #>


    [CmdletBinding()]
    [OutputType([System.Void])]
    param (

        # Specifies the path to the XCCDF benchmark file. The file must end in '*_Manual-xccdf.xml' or .zip.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript( {
                if (-Not ($_ | Test-Path) ) { throw "XCCDF does not exist" }
                if ($_ -notmatch "\-xccdf.xml|\.zip") {
                    throw "The file specified in the Path argument must be '*-xccdf.xml' or '.zip'"
                }
                return $true
            })]
        [String]
        $XCCDF,

        # Specifies the path to the orginal checklist to convert.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript( {
                if (-Not ($_ | Test-Path) ) { throw "Checklist does not exist" }
                if ($_ -notmatch "\.ckl") {
                    throw "The file specified in the Path argument must be '.ckl'"
                }
                return $true
            })]
        [Alias('FullName', 'PSPath')]
        [String]
        $OrginalChecklist,

        # Specifies the destination to save new STIG checklits. If the destination doesent exist the function will create it.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [String]
        $Destination,

        # Specifies the custom checklist name to use when creationg the new STIG checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $ChecklistName,

        # Specifies to overwrite the checklist if there is one of the same name.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $Force,

        # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default it will pull all files that end in '_Manual-xccdf.xml'. If the zip archive has multiple xccdf files you will want to set the search.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Search,

        # Specifies the Classification for the checklist. Default value is 'UNCLASSIFIED'.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('UNCLASSIFIED', 'CLASSIFIED')]
        [string]
        $Classification = "UNCLASSIFIED"
    )

    process {

        # Import Orginal Checklist
        $orginalCKL = Import-Checklist -Path $OrginalChecklist
        $oAsset = $orginalCKL.XML.CHECKLIST.ASSET
        # Create New Checklist and Import
        $newParams = [ordered]@{
            Path           = $XCCDF
            Destination    = $Destination
            Classification = $Classification
            PassThru       = $true
        }

        if ($ChecklistName) { $newParams.Add('ChecklistName', $ChecklistName) }
        if ($force) { $newParams.Add('Force', $true) }
        if ($Search) { $newParams.Add('Search', $Search) }

        if ($oAsset.ROLE ) { $newParams.Add('Role', $oAsset.ROLE) }
        if ($oAsset.ASSET_TYPE) { $newParams.Add('AssetType', $oAsset.ASSET_TYPE) }
        if ($oAsset.HOST_NAME) { $newParams.Add('HostName', $oAsset.HOST_NAME) }
        if ($oAsset.HOST_IP) { $newParams.Add('HostIP', $oAsset.HOST_IP) }
        if ($oAsset.HOST_MAC) { $newParams.Add('HostMAC', $oAsset.HOST_MAC) }
        if ($oAsset.HOST_FQDN) { $newParams.Add('HostFQDN', $oAsset.HOST_FQDN) }
        if ($oAsset.TARGET_COMMENT) { $newParams.Add('Comment', $oAsset.TARGET_COMMENT) }
        if ($oAsset.TECH_AREA) { $newParams.Add('TechArea', $oAsset.TECH_AREA) }

        #TODO: Look into a method to determine if the CKL is for a website or database
        if ($oAsset.WEB_DB_SITE) { $newParams.Add('Website', $oAsset.WEB_DB_SITE) }
        if ($oAsset.WEB_DB_INSTANCE) { $newParams.Add('DatabaseInstance', $oAsset.WEB_DB_INSTANCE) }

        $newCKL = New-Checklist @newParams

        # Get new Checklist Vulns
        $nVulns = Get-ChecklistItem -Checklist $newCKL

        # Loop through old checklists and update new checklist
        $orginalCKL.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object {

            $vuln = $_

            $id = ($vuln.STIG_DATA | Where-Object { $_.VULN_ATTRIBUTE -eq 'Vuln_Num' }).ATTRIBUTE_DATA

            # Search the new checklist for the Vuln and Legacy IDs
            $nVuln = $nVulns | Where-Object { $_.Vuln_Num -eq $id -or $_.LEGACY_ID -contains $id }

            if ($nVuln) {

                if ($nVuln.Count -eq 1) {

                    $params = @{
                        Checklist = $newCKL
                        VulnID    = $nVuln.Vuln_Num
                        Status    = $vuln.STATUS
                        Details   = $vuln.FINDING_DETAILS
                        Comments  = $vuln.COMMENTS
                    }

                    if ($vuln.Severity_Overide) {
                        $params.add('SeverityOverride', $vuln.Severity_Overide)
                        $params.add('SeverityJustification', $vuln.Severity_Justification)
                    }

                    Set-ChecklistItem @params

                }
                else {

                    Write-Verbose "Found multiple entries for VULN_ID '$id'"

                }

            }
            else {

                Write-Verbose "No VULN_ID Found for '$id'"

            }

        }

        Export-Checklist -Checklist $newCKL

    }

}
function Export-Checklist {

    <#
        .SYNOPSIS
            Export STIG Checklist to File

        .DESCRIPTION
            Export STIG Checklist to File

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                XML : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> Export-Checklist -Checklist $CKL

            This example shows how to Export a checklist. $CKL is passed with the xml data and then it is saved to the location listed in $CKL.Path.

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                XML : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> $ckl | Export-Checklist -Destination "E:\$($ckl.Name)`-v1.ckl"

            This example shows how you can use an imported checklist to export it to another file to create a copy.

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                XML : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> $ckl | Export-Checklist

            This example shows how you can use an imported checklist and pipe it to Export-Checklist. In this example, Export-Checklist will export the Checklist to whats listed in $CKL.Path
    #>


    [CmdletBinding()]
    [OutputType([System.Void])]
    param (

        # Specifies the XML Object created from Import-Checklist.
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [PSObject]
        $Checklist,

        # Specifies the path to the STIG Checklist.
        [Parameter()]
        [ValidateScript( {
                if ($_ -notmatch "\.ckl") {
                    throw "The file specified in the Checklist argument must be '.ckl'"
                }
                return $true
            })]
        [String]
        $Destination
    )

    process {

        if (!$PSBoundParameters.Destination) { $Destination = $Checklist.Path }

        try {

            $XMLSettings = New-Object -TypeName System.XML.XMLWriterSettings
            $XMLSettings.Indent = $true;
            $XMLSettings.IndentChars = "`t"
            $XMLSettings.NewLineChars = "`n"
            $XMLSettings.Encoding = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList @($false)
            $XMLSettings.ConformanceLevel = [System.Xml.ConformanceLevel]::Document

            $XMLWriter = [System.XML.XMLTextWriter]::Create("$Destination", $XMLSettings)

            $Checklist.XML.InnerXml = $Checklist.XML.InnerXml -replace "&#x0;", "[0x00]"

            $Checklist.XML.Save($XMLWriter)

        }
        catch {

            Throw $_

        }
        finally {

            if ($XMLWriter) {
                $XMLWriter.Flush()
                $XMLWriter.Dispose()
            }

        }

    }

}
function Get-ChecklistAsset {

    <#
        .SYNOPSIS
            Get Checklist Asset Information

        .DESCRIPTION
            Get Checklist Asset Information across a single checklist or multiple checklists.

        .EXAMPLE
            PS C:> Get-ChildItem -Path E:\_Temp\Test -Filter "*.ckl" | Get-ChecklistAsset

            This example demonstrates how to pull Asset Data from multiple checklists in a directory.

        .EXAMPLE
            PS C:> Get-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl"

            ROLE : Member Server
            ASSET_TYPE : Computing
            HOST_NAME : COMPUTER123
            HOST_IP : 10.0.0.4
            HOST_MAC : 00-15-5D-E8-33-9D
            HOST_FQDN : COMPUTER123.FQDN.COM
            TARGET_COMMENT : This is a test
            WEB_OR_DATABASE : false
            WEB_DB_SITE :
            WEB_DB_INSTANCE :

            This example demonstrates how to pull Asset Data from a single checklist.

        .EXAMPLE
            PS C:> Import-Checklist -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" | Get-ChecklistAsset

            ROLE : Member Server
            ASSET_TYPE : Computing
            HOST_NAME : COMPUTER123
            HOST_IP : 10.0.0.4
            HOST_MAC : 00-15-5D-E8-33-9D
            HOST_FQDN : COMPUTER123.FQDN.COM
            TARGET_COMMENT : This is a test
            WEB_OR_DATABASE : false
            WEB_DB_SITE :
            WEB_DB_INSTANCE :

            This example demonstrates how to pull Asset Data from a single checklist that was imported using Import-Checklist and piped to Get-ChecklistAsset.
    #>


    [CmdletBinding(DefaultParameterSetName = 'FromFile')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (

        # Specify the Checklist XML object to be search.
        [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)]
        [System.Object[]]
        $Checklist,

        # Specify the Checklist Path, When path is provided the function will import the checklist.
        [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'PSPath')]
        [String[]]
        $Path

    )

    process {

        if ($Path) { $Checklist = Import-Checklist -Path "$Path" }

        $asset = $Checklist.XML.CHECKLIST.ASSET

        $outputData = [ordered] @{
            ROLE            = $asset.ROLE
            ASSET_TYPE      = $asset.ASSET_TYPE
            HOST_NAME       = $asset.HOST_NAME
            HOST_IP         = $asset.HOST_IP
            HOST_MAC        = $asset.HOST_MAC
            HOST_FQDN       = $asset.HOST_FQDN
            TECH_AREA       = $asset.TECH_AREA
            TARGET_KEY      = $asset.TARGET_KEY
            TARGET_COMMENT  = $asset.TARGET_COMMENT
            WEB_OR_DATABASE = $asset.WEB_OR_DATABASE
            WEB_DB_SITE     = $asset.WEB_DB_SITE
            WEB_DB_INSTANCE = $asset.WEB_DB_INSTANCE
            ChecklistPath   = $Checklist.Path
            ChecklistName   = $Checklist.Name
        }

        #Convert to PSObject
        $assetInfo = New-Object -TypeName PSObject -Property $outputData

        # Set Custom Format for object
        $assetInfo.PSObject.TypeNames.Insert(0,'Checklist.Asset')

        Write-Output $assetInfo

    }

}
function Get-ChecklistItem {

    <#
        .SYNOPSIS
            Get Checklist item details

        .DESCRIPTION
            Get all Checklist Vulns or search for specific ones across a single checklist or multiple checklists,

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                Checklist : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> Get-ChecklistItem -Checklist $ckl

            This example shows how get all vuln items from a single checklist.

        .EXAMPLE

            PS C:\> Get-ChildItem -Path E:\_Temp\Test -Filter "*.ckl" | Get-ChecklistItem -VulnID "V-224820"

            This example shows how to use Get-ChildItem to pull .ckl files from a directory and get a specific VulnID from each checklist.

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                Checklist : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> Get-ChecklistItem -Checklist $ckl -VulnID 'V-224820'

            Output:
                HostName : COMPUTER123
                Vuln_Num : V-224820
                Status : NotAFinding
                Severity : medium
                Finding_Details : This is a detail
                Comments : This is a Comment
                Severity_Overide :
                Severity_Justification :

            This example shows how get a single vuln item from a STIG Checklist.

        .EXAMPLE
            $CKL Object
                Name : Windows_Server-2016
                STIGID : Windows_Server_2016_STIG
                Checklist : #document
                Path : E:\_Temp\Windows_Server-2016.ckl

            PS C:\> Get-ChecklistItem -Checklist $ckl -VulnID "V-224820", "V-224825"

            Output:
                HostName : COMPUTER123
                Vuln_Num : V-224820
                Status : NotAFinding
                Severity : medium
                Finding_Details : This is a detail
                Comments : This is a Comment
                Severity_Overide :
                Severity_Justification :

                HostName : COMPUTER123
                Vuln_Num : V-224825
                Status : Not_Reviewed
                Severity : medium
                Finding_Details :
                Comments :
                Severity_Overide :
                Severity_Justification :

            This example shows how get a multiple vuln items from a STIG Checklist.
    #>


    [CmdletBinding(DefaultParameterSetName = 'FromFile')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (

        # Specify the Checklist XML object to be search.
        [Parameter(ParameterSetName = "FromObject", ValueFromPipelineByPropertyName)]
        [System.Object[]]
        $Checklist,

        # Specify the Checklist Path, When path is provided the function will import the checklist.
        [Parameter(ParameterSetName = "FromFile", ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'PSPath')]
        [String[]]
        $Path,

        # Specify the STIG VulnID to search for.
        [Parameter(ParameterSetName = "FromObject", ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = "FromFile", ValueFromPipelineByPropertyName)]
        [String[]]
        $VulnID

    )

    process {

        if ($Path) { $Checklist = Import-Checklist -Path "$Path" }

        $cklHost = "$($Checklist.XML.CHECKLIST.ASSET.HOST_NAME)"
        $cklPath = $Checklist.Path

        if ($PSBoundParameters.VulnID) {

            # Poormans xPath Builder
            $xPath = "//STIG_DATA[VULN_ATTRIBUTE='Vuln_Num'"

            $count = 0

            $VulnID | ForEach-Object {

                if ($count -eq 0) {
                    $xPath = $xPath + " and ATTRIBUTE_DATA='$_'"
                }
                else {
                    $xPath = $xPath + " or ATTRIBUTE_DATA='$_'"
                }

                $count++

            }

            $xPath = $xPath + "]"

            Select-Xml -XML $Checklist.XML -XPath $xPath | ForEach-Object {
                Get-VulnItem -Object $_.Node.ParentNode -ChecklistPath $cklPath -HostName $cklHost
            }

        }
        else {

            $Checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object {
                Get-VulnItem -Object $_ -ChecklistPath $cklPath -HostName $cklHost
            }

        }

    }

}
function Get-ChecklistStatus {

    <#
        .SYNOPSIS
            Get Metrics from a STIG Checklist

        .DESCRIPTION
            Get Metrics on Total Items, Item Status, and Open CAT Findings from a STIG Checklist. CATI, CATII, and CATIII are pulled from items that are in an Open status, this wont include items that are still not reviewd.

        .EXAMPLE
            PS C:\> Get-ChecklistStatus -Path "E:\_Temp\U_MS_Windows_Server_2016_STIG_V2R2.ckl"

            This example demonstrates how to get metrics from a single

            Output
                HostName : Computer1
                STIGID : Windows_Server_2016_STIG
                FileName : U_MS_Windows_Server_2016_STIG_V2R2.ckl
                VulnCount : 273
                Open : 13
                NotAFinding : 197
                NotReviewed : 52
                NotApplicable : 11
                CATI : 2
                CATII : 11
                CATIII : 0
                VuldDetails : {NotReviewed, Open, NotAFinding, NotApplicable}
                FileInfo : {CreationTime, LastAccessTime, LastWriteTime, FileHash…}
    #>


    [CmdletBinding(DefaultParameterSetName = 'FromFile')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (

        # Specify the Checklist XML object to be search.
        [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)]
        [System.Object[]]
        $Checklist,

        # Specify the Checklist Path, When path is provided the function will import the checklist.
        [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'PSPath')]
        [String[]]
        $Path

    )

    process {

        $openFinding = @()
        $notAFinding = @()
        $notApplicable = @()
        $notReviewed = @()

        if ($Path) { $Checklist = Import-Checklist -Path "$Path" }

        $cklHostName = $($Checklist.XML.CHECKLIST.ASSET.HOST_NAME)
        $vulnCount = 0
        $catIFinding = 0
        $catIFinding = 0
        $catIIIFinding = 0

        $checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object {

            $vuln = $_
            $severity = $($vuln.STIG_DATA | Where-Object { $_.VULN_ATTRIBUTE -eq "Severity" }).ATTRIBUTE_DATA

            switch ($vuln.Status) {
                "Open" {

                    $openFinding += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName)

                    Switch -regex ($severity) {
                        'high|critical' { $catIFinding++ }
                        'medium' { $catIIFinding++ }
                        'low' { $catIIIFinding++ }
                    }

                }
                "NotAFinding" { $notAFinding += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) }
                "Not_Reviewed" { $notReviewed += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) }
                "Not_Applicable" { $notApplicable += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) }
            }

            $vulnCount++
        }

        # Get File Details
        $cklFile = Get-Item -Path $Checklist.Path
        $cklFileHash = Get-FileHash -Path $Checklist.Path

        $cklFileDetails = [Ordered]@{
            CreationTime   = $cklFile.CreationTime
            LastAccessTime = $cklFile.LastAccessTime
            LastWriteTime  = $cklFile.LastWriteTime
            FileHash       = $cklFileHash.Hash
            HashAlgorithm  = $cklFileHash.Algorithm
        }

        # Compile Data
        $params = [Ordered]@{
            HostName      = $cklHostName
            STIGID        = $(Select-Xml -Xml $Checklist.XML -XPath "//SI_DATA[SID_NAME='stigid']").Node.SID_Data
            FileName      = $(Split-Path -Path $Checklist.Path -Leaf)
            VulnCount     = $vulnCount
            Open          = $openFinding.Count
            NotAFinding   = $notAFinding.Count
            NotReviewed   = $notReviewed.Count
            NotApplicable = $notApplicable.Count
            CATI          = $catIFinding
            CATII         = $catIIFinding
            CATIII        = $catIIIFinding
            VulnDetails   = @{
                Open          = $openFinding
                NotAFinding   = $notAFinding
                NotReviewed   = $notReviewed
                NotApplicable = $notApplicable
            }
            FileInfo      = $cklFileDetails
        }

        Write-Output $(New-Object -TypeName psobject -Property $params)

    }

}
Function Import-Checklist {

    <#
        .SYNOPSIS
            Import STIG Checklist (CKL)

        .DESCRIPTION
            Load a CKL file as an [XML] element. This can then be passed to other functions in this module.

        .EXAMPLE
            PS:> $ckl = Import-Checklist -Checklist C:\Temp\U_Windows10.ckl

            This example shows how to import a checklist.

            OUTPUT
            ------
            Name : Path
            STIGID : IIS_10-0_Site_STIG
            Checklist : #document
            Path : E:\_Temp\Checklists\U_MS_IIS_10-0_Site_STIG_V2R2.ckl
    #>


    [CmdletBinding()]
    [OutputType([PSObject])]
    Param(

        # Specify the path to the STIG Checklist. When function is ran, it will validate the file passed exists and is a .ckl file.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript( {
                if (-Not ($_ | Test-Path) ) {
                    throw "Checklist does not exist"
                }
                if ($_ -notmatch "\.ckl") {
                    throw "The file specified in the Checklist argument must be '.ckl'"
                }
                return $true
            })]
        [Alias('FullName', 'PSPath')]
        [String]
        $Path

    )

    process {

        $results = Test-XMLFile -XmlPath $Path -SchemaPath $cklSchemaPath

        If ($results.Valid -eq $false) {
            throw $results.Exceptions
        }
        else {

            $xml = [XML](Get-Content -Encoding UTF8 -Path $Path)

            $params = [Ordered]@{
                Name   = (Split-Path $Path -Leaf).Split(".")[0]
                STIGID = (Select-Xml -Xml $xml.Checklist -XPath "//SI_DATA[SID_NAME='stigid']").Node.SID_Data
                XML    = $xml
                Path   = $Path
            }

            #Convert to PSObject
            $import = New-Object -TypeName PSObject -Property $params

            # Set Custom Format for object
            $import.PSObject.TypeNames.Insert(0, 'Checklist.Import')

            Write-Output $import

        }

    }

}
function New-Checklist {

    <#
        .SYNOPSIS
            Create a New STIG Checklist

        .DESCRIPTION
            This function creates a new STIG Checklist from the DoD Cyber Exchange.

        .EXAMPLE
            PS C:> $ckl = New-Checklist -Path "E:\_Temp\U_MS_Windows_Server_2016_V2R2_STIG.zip" -Destination E:\_Temp -PassThru

            This example shows how to create a new checklist from the zip file downloaded from Cyber.mil. This example also leverages the -PassThru switch that will output the Checklist object to use with other functions.

        .EXAMPLE
            Directory: E:\_Temp\Test

            Mode LastWriteTime Length Name
            ---- ------------- ------ ----
            -a--- 5/12/2021 9:56 AM 535864 U_A10_Networks_ADC_ALG_V2R1_STIG.zip
            -a--- 5/12/2021 9:56 AM 276032 U_A10_Networks_ADC_NDM_V1R1_STIG.zip
            -a--- 5/12/2021 9:56 AM 681814 U_AAA_Services_V1R2_SRG.zip
            -a--- 5/12/2021 9:56 AM 640328 U_Active_Directory_Domain_V2R13_STIG.zip
            -a--- 5/12/2021 9:56 AM 444331 U_Active_Directory_Forest_V2R8_STIG.zip
            -a--- 5/12/2021 9:57 AM 680792 U_Adobe_Acrobat_Pro_DC_Classic_V2R1_STIG.zip

            PS C:>Get-ChildItem -Path E:\_Temp\Test -Filter "*.zip" | New-Checklist -Destination E:\_Temp\Checklists

            This example shows how to pull a directory full of zip files from Cyber.mil and convert them to Checklists. This will use the name of the zip file for the Checklist name

            Directory: E:\_Temp\Checklists

            Mode LastWriteTime Length Name
            ---- ------------- ------ ----
            -a--- 5/19/2021 8:20 AM 186660 U_A10_Networks_ADC_ALG_STIG_V2R1.ckl
            -a--- 5/19/2021 8:20 AM 214704 U_A10_Networks_ADC_NDM_STIG_V1R1.ckl
            -a--- 5/19/2021 8:20 AM 371142 U_AAA_Services_SRG_V1R2.ckl
            -a--- 5/19/2021 8:20 AM 207120 U_Active_Directory_Domain_STIG_V2R13.ckl
            -a--- 5/19/2021 8:20 AM 33416 U_Active_Directory_Forest_STIG_V2R8.ckl
            -a--- 5/19/2021 8:20 AM 141849 U_Adobe_Acrobat_Pro_DC_Classic_V2R1.ckl

    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'None')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (

        # Specifies the path to the XCCDF Benchmark file. The file must end in '*_Manual-xccdf.xml' or .zip.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "FromFile")]
        [ValidateScript( {
                if (-Not ($_ | Test-Path) ) { throw "XCCDF does not exist" }
                if ($_ -notmatch "\-xccdf.xml|\.zip") {
                    throw "The file specified in the Path argument must be '*-xccdf.xml' or '.zip'"
                }
                return $true
            })]
        [Alias('FullName', 'PSPath')]
        [String]
        $Path,

        # Specifies the destination to save new STIG Checklits. If the Destination doesent exist the function will create it.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [String]
        $Destination,

        # Specifies the custom Checklist name to use when creationg the Stig Checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $ChecklistName,

        # Specifies the Classification for the checklist. Default value is 'UNCLASSIFIED'.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('UNCLASSIFIED', 'CLASSIFIED')]
        [string]
        $Classification = "UNCLASSIFIED",

        # Specifies to overwrite the Checklist if there is one of the same name.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $Force,

        # Specifies if the function should return the checklist information
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $PassThru,

        # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default it will pull all files that end in '_Manual-xccdf.xml'. If the zip archive has multiple xccdf files you will want to set the search.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Search,

        # Specifies the host name for the checklist for either computer or non-computer checklists.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $HostName,

        # Specifies the Host IP Address for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $HostIP,

        # Specifies the Host MAC Address for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $HostMAC,

        # Specifies the Host FQDN for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $HostFQDN,

        # Specifies the Target Comment for the given host.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Comment,

        # Specifies the Host WebSite for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $WebSite,

        # Specifies the Host Database Instance for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $DatabaseInstance,

        # Specifies the Host Database for Checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Database,

        # Specifies the Asset Type for the checklist. Default value is Computing.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Non-Computing', 'Computing')]
        [String]
        $AssetType = 'Computing',

        # Specifies the Asset Role for the checklist. Default value is 'None'.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('None', 'Workstation', 'Member Server', 'Domain Controller')]
        [String]
        $Role,

        # Specifies the Technology Area the STIG Applies to.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet(
            "Application Review",
            "Boundary Security",
            "CDS Admin Review",
            "CDS Technical Review",
            "Database Review",
            "Domain Name System (DNS)",
            "Exchange Server",
            "Host Based System Security (HBSS)",
            "Internal Network",
            "Mobility",
            "Releasable Networks (REL)",
            "Traditional Security",
            "UNIX OS",
            "VVOIP Review",
            "Web Review",
            "Windows OS",
            "Other Review"
        )]
        [String]
        $TechnologyArea

    )

    begin {

        function Get-DisscussionItem ($Data, $Search) {

            $(Select-String -InputObject $Data -Pattern "<$Search>(.|\n)*?</$Search>" |
                    ForEach-Object { $_.Matches } | ForEach-Object { $_.Value }) -replace "<(/|)$Search>"

        }

        # Create XML Settings
        $xmlWriterSettings = [System.Xml.XmlWriterSettings]::new()
        $xmlWriterSettings.Indent = $true
        $xmlWriterSettings.IndentChars = "`t"
        $xmlWriterSettings.NewLineChars = "`n"

    }

    process {

        $xccdf = @()
        $pathExtenstion = $($path.Split(".")[-1]).ToLower()

        # Test Destination Path, Create if Missing
        if (!(Test-Path -Path $Destination)) {
            New-Item -Path $Destination -ItemType Directory | Out-Null
            Write-Verbose "[$($MyInvocation.MyCommand)] Created Missing Destination Directory"
        }

        switch ($pathExtenstion) {
            'zip' {

                $params = @{
                    Path = $Path
                }

                if ($Search) { $params.add('Search', $Search) }

                $xccdf += Get-XCCDFFromArchive @params

            }
            'xml' {

                $params = [Ordered]@{
                    Name = (Split-Path $Path -Leaf).Split(".")[0]
                    Path = $Path
                    XML  = [XML](Get-Content -Encoding UTF8 -Path $Path)
                }

                $xccdf += New-Object -TypeName PSObject -Property $params

            }

        }

        $xccdf | ForEach-Object {

            $benchmark = $_

            $defaultName = $benchmark.Name -replace "_Manual-xccdf.xml"

            # Generate Checklist Name if not Provided
            $fileName = if ($PSBoundParameters.ChecklistName) {
                $ChecklistName
            }
            else {

                if ($PSBoundParameters.HostName) {

                    $fnParams = @{
                        ChecklistName = $defaultName
                        HostName      = $HostName
                    }

                    if ($PSBoundParameters.WebSite) {
                        $fnParams.add('Website', $WebSite)
                    }
                    elseif ($PSBoundParameters.Database) {
                        $fnParams.add('Database', $Database)
                    }
                    elseif ($PSBoundParameters.DatabaseInstance) {
                        $fnParams.add('DatabaseInstance', $DatabaseInstance)
                    }

                    New-ChecklistFileName @fnParams

                }
                else {
                    "{0}.ckl" -f $defaultName
                }

            }

            [System.IO.FileInfo]$cklPath = Join-Path -Path $Destination -ChildPath $fileName

            # Check if a file already exists
            if ($(Test-Path $cklPath.FullName) -eq $true -and !$force) {

                $errMessage = @{
                    Message     = "Another Checklist with the Same name '$fileName' already exist at the destination. Use -Force to overide it."
                    ErrorID     = "SAM0001"
                    Category    = "ResourceExists"
                    ErrorAction = "Stop"
                }

                Write-Error @errMessage

            }
            elseif ($(Test-Path $cklPath.FullName) -eq $true -and $Force) {

                if ($PSCmdlet.ShouldProcess($cklPath.FullName, "Remove Old checklist")) {

                    Remove-Item -Path $cklPath.FullName -Force -ErrorAction Stop -Confirm:$false | Out-Null

                }

            }

            # Generate Checklist
            $xccdfBenchmark = $([xml]$benchmark.XML).Benchmark
            $xccdfFileName = $benchmark.Name
            $stigID = $xccdfBenchmark.id

            [xml]$cklSchema = Get-Content -Path $cklSchemaPath

            if ($PSCmdlet.ShouldProcess($cklPath.FullName, "Create Checklist")) {

                $writer = [System.Xml.XmlWriter]::Create($cklPath.FullName, $xmlWriterSettings)
                $module = $(Get-Command -Name $MyInvocation.MyCommand).ModuleName

                $writer.WriteComment(" Created by '$module' - Version: $((Get-Module -Name $module).Version.ToString()) ")
                $writer.WriteComment(" Following Cyber.Mil STIGViewer Checklist Schema V$($cklSchema.schema.annotation.appinfo.version) - $($cklSchema.schema.annotation.appinfo.date)")

                # [Checklist Element] -----------------------------------------------------
                $writer.WriteStartElement('CHECKLIST')

                # [Asset Element] -----------------------------------------------------
                $writer.WriteStartElement("ASSET")

                $assetData = [ordered] @{
                    'ROLE'            = "None"
                    'ASSET_TYPE'      = "$AssetType"
                    'HOST_NAME'       = ""
                    'HOST_IP'         = ""
                    'HOST_MAC'        = ""
                    'HOST_FQDN'       = ""
                    'TARGET_COMMENT'  = ""
                    'TECH_AREA'       = ""
                    'TARGET_KEY'      = $($xccdfBenchmark.Group[0].Rule.reference.identifier)
                    'WEB_OR_DATABASE' = 'false'
                    'WEB_DB_SITE'     = ""
                    'WEB_DB_INSTANCE' = ""
                }

                if ($PSBoundParameters.Role) { $assetData.ROLE = $Role }
                if ($PSBoundParameters.AssetType) { $assetData.ASSET_TYPE = $AssetType }
                if ($PSBoundParameters.HostName) { $assetData.HOST_NAME = $HostName }

                if ($AssetType -eq 'Computing') {

                    if ($PSBoundParameters.HostIP) { $assetData.HOST_IP = $HostIP }
                    if ($PSBoundParameters.HostMAC) { $assetData.HOST_MAC = $HostMAC }
                    if ($PSBoundParameters.HostFQDN) { $assetData.HOST_FQDN = $HostFQDN }
                    if ($PSBoundParameters.Comment) { $assetData.TARGET_COMMENT = $Comment }
                    if ($PSBoundParameters.TechnologyArea) { $assetData.TECH_AREA = $TechnologyArea }

                    if ($PSBoundParameters.WebSite) {
                        $assetData.WEB_OR_DATABASE = 'true'
                        $assetData.WEB_DB_SITE = $Website
                    }

                    if ($PSBoundParameters.DatabaseInstance) {
                        $assetData.WEB_OR_DATABASE = 'true'
                        $assetData.WEB_DB_INSTANCE = $DatabaseInstance
                    }

                    if ($PSBoundParameters.Database) {
                        $assetData.WEB_OR_DATABASE = 'true'
                        $assetData.WEB_DB_SITE = $Database
                    }
                }

                # Create Asset Elements
                foreach ($asset in $assetData.GetEnumerator()) {
                    $writer.WriteStartElement($asset.name)
                    $writer.WriteString($asset.value)
                    $writer.WriteEndElement()
                }

                $writer.WriteEndElement(<#ASSET#>)
                $writer.WriteStartElement("STIGS")
                $writer.WriteStartElement("iSTIG")
                $writer.WriteStartElement("STIG_INFO")

                $cklInfo = [ordered] @{
                    'version'        = $xccdfBenchmark.version
                    'classification' = $Classification
                    'customname'     = ""
                    'stigid'         = $xccdfBenchmark.id
                    'description'    = $xccdfBenchmark.description #$(ConvertTo-SafeXML -String $xccdfBenchmark.description)
                    'filename'       = $XCCDFFileName
                    'releaseinfo'    = $xccdfBenchmark.'plain-text'.'#text'
                    'title'          = $xccdfBenchmark.title
                    'uuid'           = (New-Guid).Guid
                    'notice'         = $xccdfBenchmark.notice.id
                    'source'         = $xccdfBenchmark.reference.source
                }

                foreach ($info in $cklInfo.GetEnumerator()) {

                    $writer.WriteStartElement("SI_DATA")

                    $writer.WriteStartElement('SID_NAME')
                    $writer.WriteString($info.name)
                    $writer.WriteEndElement(<#SID_NAME#>)

                    if ($info.value -ne "") {
                        $writer.WriteStartElement('SID_DATA')
                        $writer.WriteString($info.value)
                        $writer.WriteEndElement(<#SID_DATA#>)
                    }

                    $writer.WriteEndElement(<#SI_DATA#>)

                }

                $writer.WriteEndElement(<#STIG_INFO#>)

                $xccdfBenchmark.Group | ForEach-Object {

                    $vulnId = $_
                    $stigRef = "{0} :: Version {1}, {2}" -f $cklInfo.Title, $cklInfo.Version, $cklInfo.ReleaseInfo
                    $writer.WriteStartElement("VULN")

                    $cciList = $_.Rule.ident | Where-Object { $_.System -eq 'http://iase.disa.mil/cci' }
                    $legList = $_.Rule.ident | Where-Object { $_.system -eq 'http://cyber.mil/legacy' }

                    $vulnInfo = [ordered] @{
                        Vuln_Num                   = $vulnId.id
                        Severity                   = $vulnId.Rule.severity
                        Group_Title                = $vulnId.title
                        Rule_ID                    = $vulnId.Rule.id
                        Rule_Ver                   = $vulnId.Rule.version
                        Rule_Title                 = $vulnId.Rule.title
                        Vuln_Discuss               = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "VulnDiscussion")
                        IA_Controls                = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "IAControls")
                        Check_Content              = $vulnId.Rule.check.'check-content'
                        Fix_Text                   = $vulnId.Rule.fixtext.InnerText
                        False_Positives            = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "FalsePositives")
                        False_Negatives            = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "FalseNegatives")
                        Documentable               = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Documentable")
                        Mitigations                = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Mitigations")
                        Potential_Impact           = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "PotentialImpacts")
                        Third_Party_Tools          = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "ThirdPartyTools")
                        Mitigation_Control         = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "MitigationControl")
                        Responsibility             = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Responsibility")
                        Security_Override_Guidance = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "SeverityOverrideGuidance")
                        Check_Content_Ref          = $vulnId.Rule.check.'check-content-ref'.name
                        Weight                     = $vulnId.Rule.Weight
                        Class                      = 'Unclass'
                        STIGRef                    = $stigRef
                        TargetKey                  = $_.Rule.reference.identifier
                        STIG_UUID                  = (New-Guid).Guid
                    }

                    foreach ($vuln in $vulnInfo.GetEnumerator()) {

                        $writer.WriteStartElement("STIG_DATA")

                        $writer.WriteStartElement("VULN_ATTRIBUTE")
                        $writer.WriteString($vuln.Name)
                        $writer.WriteEndElement(<#VULN_ATTRIBUTE#>)

                        $writer.WriteStartElement("ATTRIBUTE_DATA")
                        $writer.WriteString($vuln.Value)
                        $writer.WriteEndElement(<#ATTRIBUTE_DATA#>)

                        $writer.WriteEndElement(<#STIG_DATA#>)

                    }

                    if ($legList) {

                        $legList.'#text' | ForEach-Object {

                            $writer.WriteStartElement("STIG_DATA")

                            $writer.WriteStartElement("VULN_ATTRIBUTE")
                            $writer.WriteString('LEGACY_ID')
                            $writer.WriteEndElement(<#VULN_ATTRIBUTE#>)

                            $writer.WriteStartElement("ATTRIBUTE_DATA")
                            $writer.WriteString($_)
                            $writer.WriteEndElement(<#ATTRIBUTE_DATA#>)

                            $writer.WriteEndElement(<#STIG_DATA#>)

                        }

                    }
                    else {

                        # Mimics STIGViewer and put two empty Legacy_IDs.
                        $writer.WriteStartElement("STIG_DATA")

                        $writer.WriteStartElement("VULN_ATTRIBUTE")
                        $writer.WriteString('LEGACY_ID')
                        $writer.WriteEndElement(<#VULN_ATTRIBUTE#>)

                        $writer.WriteStartElement("ATTRIBUTE_DATA")
                        $writer.WriteString("")
                        $writer.WriteEndElement(<#ATTRIBUTE_DATA#>)

                        $writer.WriteEndElement(<#STIG_DATA#>)

                        $writer.WriteStartElement("STIG_DATA")

                        $writer.WriteStartElement("VULN_ATTRIBUTE")
                        $writer.WriteString('LEGACY_ID')
                        $writer.WriteEndElement(<#VULN_ATTRIBUTE#>)

                        $writer.WriteStartElement("ATTRIBUTE_DATA")
                        $writer.WriteString("")
                        $writer.WriteEndElement(<#ATTRIBUTE_DATA#>)

                        $writer.WriteEndElement(<#STIG_DATA#>)
                    }

                    $cciList.'#text' | ForEach-Object {

                        $writer.WriteStartElement("STIG_DATA")

                        $writer.WriteStartElement("VULN_ATTRIBUTE")
                        $writer.WriteString('CCI_REF')
                        $writer.WriteEndElement(<#VULN_ATTRIBUTE#>)

                        $writer.WriteStartElement("ATTRIBUTE_DATA")
                        $writer.WriteString($_)
                        $writer.WriteEndElement(<#ATTRIBUTE_DATA#>)

                        $writer.WriteEndElement(<#STIG_DATA#>)

                    }

                    $writer.WriteStartElement("STATUS")
                    $writer.WriteString('Not_Reviewed')
                    $writer.WriteEndElement(<#STATUS#>)

                    $writer.WriteStartElement("FINDING_DETAILS")
                    $writer.WriteString('')
                    $writer.WriteEndElement(<#FINDING_DETAILS#>)

                    $writer.WriteStartElement("COMMENTS")
                    $writer.WriteString('')
                    $writer.WriteEndElement(<#COMMENTS#>)

                    $writer.WriteStartElement("SEVERITY_OVERRIDE")
                    $writer.WriteString('')
                    $writer.WriteEndElement(<#SEVERITY_OVERRIDE#>)

                    $writer.WriteStartElement("SEVERITY_JUSTIFICATION")
                    $writer.WriteString('')
                    $writer.WriteEndElement(<#SEVERITY_JUSTIFICATION#>)

                    $writer.WriteEndElement(<#VULN#>)
                }

                $writer.WriteEndElement(<#iSTIG#>)

                $writer.WriteEndElement(<#STIGS#>)

                $writer.WriteEndElement(<#CHECKLIST#>)

                # Close XML Writer
                $writer.Flush()
                $writer.Close()

                # Test Checklist
                $results = Test-XMLFile -XmlPath $cklPath.FullName -SchemaPath $cklSchemaPath

                If ($results.Valid -eq $false) {
                    throw $results.Exceptions
                }
                else {

                    if ($PassThru) {

                        $params = [Ordered]@{
                            Name   = $cklPath.BaseName
                            STIGID = $stigID
                            XML    = [XML](Get-Content -Encoding UTF8 -Path $cklPath.FullName)
                            Path   = $cklPath.FullName
                        }

                        #Convert to PSObject
                        $import = New-Object -TypeName PSObject -Property $params

                        # Set Custom Format for object
                        $import.PSObject.TypeNames.Insert(0, 'Checklist.Import')

                        Write-Output $import

                    }

                }

            }

        }

    }

}
function Set-ChecklistAsset {

    <#
        .SYNOPSIS
            Sets Checklist Asset Information

        .DESCRIPTION
            Sets Checklist Asset Information across a single checklist or multiple checklists.

        .EXAMPLE
            PS E:> Set-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" -HostName Computer123 -HostIP 10.0.0.32

            This example demonstrates how to set the Hostname and Host IP for a given checklist.

        .EXAMPLE

            PS E:> Set-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" -HostName Computer123 -HostIP 10.0.0.32 -PassThru

            This example demonstrates how to set the Hostname and Host IP for a set of checklists in a directory.

    #>


    [CmdletBinding(DefaultParameterSetName = 'FromFile')]
    [OutputType([System.Void])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseDeclaredVarsMoreThanAssignments', '',
        Justification = 'Several Parameters are used just to trigger an error'
    )]
    param (

        # Specifies the Checklist XML object to be updated.
        [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)]
        [PSObject]
        $Checklist,

        # Specifies the Checklist Path, When path is provided the function will import the checklist.
        [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'PSPath')]
        [String]
        $Path,

        # Specifies the host name for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $HostName,

        # Specifies the Host IP Address for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $HostIP,

        # Specifies the Host MAC Address for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $HostMAC,

        # Specifies the Host FQDN for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $HostFQDN,

        # Specifies the Target Comment for the given host.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $Comment,

        # Specifies the Host WebSite for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $WebSite,

        # Specifies the Host Database Instance for the checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $DatabaseInstance,

        # Specifies the Host Database for Checklist.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $Database,

        # Specifies the Asset Type for the checklist..
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Non-Computing', 'Computing')]
        [string]
        $AssetType,

        # Specifies the Asset Role for the checklist. Default value is 'None'.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('None', 'Workstation', 'Member Server', 'Domain Controller')]
        [string]
        $Role,

        # Specifies the Technology Area the STIG Applies to.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet(
            "Application Review",
            "Boundary Security",
            "CDS Admin Review",
            "CDS Technical Review",
            "Database Review",
            "Domain Name System (DNS)",
            "Exchange Server",
            "Host Based System Security (HBSS)",
            "Internal Network",
            "Mobility",
            "Releasable Networks (REL)",
            "Traditional Security",
            "UNIX OS",
            "VVOIP Review",
            "Web Review",
            "Windows OS",
            "Other Review"
        )]
        [string]
        $TechnologyArea

    )

    process {

        if ($Path) { $Checklist = Import-Checklist -Path "$Path" }

        $modified = $false
        $asset = $Checklist.XML.CHECKLIST.ASSET

        If ($HostName) {

            $escaped = $(ConvertTo-SafeXML -String $HostName)

            if ( $asset.HOST_NAME -ne $escaped ) {
                $asset.HOST_NAME = "$($escaped.ToUpper())"
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - HostName '$($asset.HOST_NAME)' already matches '$escaped', no change."
            }

        }

        If ($HostIP) {

            # Test if valid IP Address
            try {

                # This will throw an error if its in the wrong format.
                [System.Net.IPAddress]$IP = $HostIP

                if ( $asset.HOST_IP -ne $HostIP ) {
                    $asset.HOST_IP = "$HostIP"
                    $modified = $true
                }
                else {
                    Write-Verbose "$($Checklist.Name) - HostIP '$($asset.HOST_IP)' already matches '$HostIP', no change."
                }

            }
            catch {
                Write-Verbose "$($Checklist.Name) - HostIP - $_"
            }

        }

        if ($HostMAC) {

            if ($HostMAC -match $regexMAC) {

                $escaped = $(ConvertTo-SafeXML -String $HostMAC)

                if ( $asset.HOST_MAC -ne $escaped ) {
                    $asset.HOST_MAC = "$escaped"
                    $modified = $true
                }
                else {
                    Write-Verbose "$($Checklist.Name) - MAC '$($asset.HOST_MAC)' already matches '$escaped', no change."
                }

            }
            else {
                Write-Verbose "$($Checklist.Name) - MAC '$HostMAC' is not a valid format, no change."
            }

        }

        if ($HostFQDN) {

            if ($HostFQDN -match $regexFQDN) {

                $escaped = $(ConvertTo-SafeXML -String $HostFQDN)

                if ( $asset.HOST_FQDN -ne $escaped ) {
                    $asset.HOST_FQDN = "$($escaped.ToUpper())"
                    $modified = $true
                }
                else {
                    Write-Verbose "$($Checklist.Name) - FQDN '$($asset.HOST_FQDN)' already matches '$escaped', no change."
                }

            }
            else {
                Write-Verbose "$($Checklist.Name) - FQDN '$HostFQDN' is not a valid format, no change."
            }

        }

        if ($Comment) {

            $escaped = $(ConvertTo-SafeXML -String $Comment)

            if ( $asset.TARGET_COMMENT -ne $escaped ) {
                $asset.TARGET_COMMENT = "$escaped"
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Comment '$($asset.TARGET_COMMENT)' already matches '$escaped', no change."
            }

        }

        if ($Role) {

            $escaped = $(ConvertTo-SafeXML -String $Role)

            if ( $asset.ROLE -ne $escaped) {
                $asset.ROLE = "$escaped"
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Role '$($asset.ROLE)' already matches '$escaped', no change."
            }

        }

        if ($Website) {

            $escaped = $(ConvertTo-SafeXML -String $WebSite)

            if ( $asset.WEB_DB_SITE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') {
                $asset.WEB_DB_SITE = "$($escaped.ToUpper())"
                $asset.WEB_OR_DATABASE = 'true'
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Website '$($asset.WEB_DB_SITE)' already matches '$escaped', no change."
            }

        }

        If ($Database) {

            $escaped = $(ConvertTo-SafeXML -String $Database)

            if ( $asset.WEB_DB_SITE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') {
                $asset.WEB_DB_SITE = "$escaped"
                $asset.WEB_OR_DATABASE = 'true'
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Database '$($asset.WEB_DB_SITE)' already matches '$escaped', no change."
            }

        }

        If ($DatabaseInstance) {

            $escaped = $(ConvertTo-SafeXML -String $DatabaseInstance)

            if ( $asset.WEB_DB_INSTANCE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') {
                $asset.WEB_DB_INSTANCE = "$escaped"
                $asset.WEB_OR_DATABASE = 'true'
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Database Instance '$($asset.WEB_DB_INSTANCE)' already matches '$escaped', no change."
            }

        }

        if ($AssetType) {

            $escaped = $(ConvertTo-SafeXML -String $AssetType)

            if ( $asset.ASSET_TYPE -ne $escaped ) {

                if ($AssetType -eq "Non-Computing") {
                    $asset.HOST_IP = ""
                    $asset.HOST_MAC = ""
                    $asset.HOST_FQDN = ""
                    $asset.TARGET_COMMENT = ""
                    $asset.ROLE = 'None'
                    $asset.WEB_OR_DATABASE = 'false'
                    $asset.WEB_DB_SITE = ""
                    $asset.WEB_DB_INSTANCE = ""
                }

                $asset.ASSET_TYPE = "$escaped"
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - ASSET_TYPE '$($asset.ASSET_TYPE)' already matches '$escaped', no change."
            }

        }

        if ($TechnologyArea) {

            $escaped = $(ConvertTo-SafeXML -String $TechnologyArea)

            if ( $asset.TECH_AREA -ne $escaped) {
                $asset.TECH_AREA = "$escaped"
                $modified = $true
            }
            else {
                Write-Verbose "$($Checklist.Name) - Technology Area '$($asset.TECH_AREA)' already matches '$escaped', no change."
            }

        }

        If ($modified -eq $true -and $Path) {

            Write-Verbose "$($Checklist.Name) was modified, saving file changes"
            Export-Checklist -Checklist $Checklist
            Write-Verbose "Saved Changes to '$($Checklist.Path)'"

        }

    }

}
function Set-ChecklistItem {

    <#
        .SYNOPSIS
            Set Checklist item details

        .DESCRIPTION
            Set Checklist item details for a single or multiple checklists.

        .EXAMPLE
            PS C:\> Set-ChecklistItem -Path "E:\_Temp\U_A10_Networks_ADC_ALG_STIG_V2R1.ckl" -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding

            This example demonstrates how to set a single checklist item.

        .EXAMPLE
            PS C:\> Get-ChildItem -Path E:\_Temp\Checklists -Filter "*.ckl" | Set-ChecklistItem -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding

            This example demonstrates how to set a single checklist item on multiple checklists of the same type.

        .EXAMPLE
            PS C:\> $ckl = Import-Checklist -Path "E:\_Temp\U_A10_Networks_ADC_ALG_STIG_V2R1.ckl"
            PS C:\> Set-ChecklistItem -Checklist $ckl -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding
            PS C:\> $ckl | Export-Checklist

            This example demonstrates how to import a checklist, calling Set-ChecklistItem, and then exporting the checklist to save the changes. This is useful for when your setting multiple items and want to save time on save to the file each time a change is made.
    #>


    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (

        # Specifies the Checklist XML object to be search.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName="FromImport")]
        [System.Object[]]
        $Checklist,

        # Specifies the Checklist Path, When path is provided the function will import the checklist.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName="FromFile")]
        [Alias('FullName', 'PSPath')]
        [String[]]
        $Path,

        # Specifies the STIG VulnID to update.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $VulnID,

        # Specifies the Details to update.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Details,

        # Specifies the Comments to update.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $Comments,

        # Specifies the Status of the Checklist Item.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet("Open", "NotAFinding", "Not_Reviewed", "Not_Applicable")]
        [String]
        $Status,

        # Specifies the Severity Override value for the checklist item.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet("High", "Medium", "Low")]
        [String]
        $SeverityOverride,

        # Specifies the Severity Override Justification for the checklist item.
        [Parameter(ValueFromPipelineByPropertyName)]
        [String]
        $SeverityJustification

    )

    process {

        $modified = $false

        if ($Path) { $Checklist = Import-Checklist -Path "$Path" }

        $xPath = "//STIG_DATA[VULN_ATTRIBUTE='Vuln_Num' and ATTRIBUTE_DATA='$VulnID']"

        Select-Xml -Xml $Checklist.XML -XPath $xPath | ForEach-Object {

            $item = $_.Node.ParentNode

            if ($PSBoundParameters.Details) {

                $escaped = $(ConvertTo-SafeXML -String $Details)

                if ( $item.FINDING_DETAILS -ne $escaped ) {
                    $item.FINDING_DETAILS = "$escaped"
                    $modified = $true
                }
                else {
                    Write-Verbose "$vulnId - FINDING_DETAILS '$($item.FINDING_DETAILS)' already matches '$escaped', no change."
                }

            }

            if ($PSBoundParameters.Comments) {

                $escaped = $(ConvertTo-SafeXML -String $Comments)

                if ( $item.COMMENTS -ne $escaped ) {
                    $item.COMMENTS = "$escaped"
                    $modified = $true
                }
                else {
                    Write-Verbose "$vulnId - COMMENTS '$($item.COMMENTS)' already matches '$escaped', no change."
                }

            }

            if ($PSBoundParameters.Status) {

                if ( $item.STATUS -ne $STATUS ) {
                    $item.STATUS = "$STATUS"
                    $modified = $true
                }
                else {
                    Write-Verbose "$vulnId - STATUS '$($item.STATUS)' already matches '$Status', no change."
                }

            }

            if ($PSBoundParameters.SeverityOverride) {

                $escaped = $(ConvertTo-SafeXML -String $SeverityOverride)

                if ( $item.SeverityOverride -ne $escaped ) {
                    $item.SeverityOverride = "$escaped"
                    $modified = $true
                }
                else {
                    Write-Verbose "$vulnId - SeverityOverride '$($item.SeverityOverride)' already matches '$escaped', no change."
                }

            }

            if ($PSBoundParameters.SeverityJustification) {

                $escaped = $(ConvertTo-SafeXML -String $SeverityJustification)

                if ( $item.SeverityJustification -ne $escaped ) {
                    $item.SeverityJustification = "$escaped"
                    $modified = $true
                }
                else {
                    Write-Verbose "$vulnId - SeverityOverride '$($item.SeverityJustification)' already matches '$escaped', no change."
                }

            }

        }

        If ($modified -eq $true -and $Path) {

            Write-Verbose "$($Checklist.Name) was modified, saving file changes"
            Export-Checklist -Checklist $Checklist
            Write-Verbose "Saved Changes to '$($Checklist.Path)'"

        }

    }

}

function ConvertTo-SafeXml {

    <#
        .SYNOPSIS
            Escapes invalid characters in the input to create safe XML output.

        .DESCRIPTION
            Escapes invalid characters in the input to create safe XML output.

        .EXAMPLE
            $escaped = $(ConvertTo-SafeXML -String $Database)

            This example shows how to call the function.

    #>


    [CmdletBinding()]
    [OutputType([string])]
    param(

        # Specify the text string to escape invlaid characters.
        [Parameter(Mandatory)]
        [String]
        [AllowEmptyString()]
        $String

    )

    Write-Output $([System.Security.SecurityElement]::Escape($String))

}

function Get-VulnItem {

    <#
        .SYNOPSIS
            Converts xml Object to Checklist.Item

        .EXAMPLE
            $Checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object {
                Get-VulnItem -Object $_ -ChecklistPath $cklPath -HostName $cklHost
            }

            This example demonstrates how to call the function.

    #>


    param (

        # Specifies the Vuln Object to convert.
        [Parameter(Mandatory)]
        [System.Object]
        $object,

        # Specifies the HostName for the checklist
        [Parameter()]
        [String]
        $HostName = "",

        # Specifies the Checklist Path
        [Parameter(Mandatory)]
        [String]
        $ChecklistPath

    )

    $checklistName = Split-Path -Path $ChecklistPath -Leaf

    $vulnId = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "Vuln_Num" }).ATTRIBUTE_DATA

    $vuln = [Ordered]@{
        HostName               = $HostName
        Vuln_Num               = $vulnId
        Status                 = $object.Status
        Finding_Details        = $object.Finding_Details
        Comments               = $object.Comments
        Severity_Overide       = $object.Severity_Override
        Severity_Justification = $object.Severity_Justification
        ChecklistPath          = $ChecklistPath
        Checklist              = $checklistName
    }

    $object.STIG_Data | ForEach-Object {
        if ($_.VULN_ATTRIBUTE -notmatch "Vuln_Num|CCI_REF|LEGACY_ID") {
            $vuln.add($_.VULN_ATTRIBUTE, $_.ATTRIBUTE_DATA)
        }
    }

    # Add CCI and LegacyID
    $cci = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "CCI_REF" }).ATTRIBUTE_DATA
    $lID = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "LEGACY_ID" }).ATTRIBUTE_DATA

    $vuln.add("CCI_REF", $cci)
    $vuln.add("LEGACY_ID", $lID)

    #Convert to PSObject
    $vulnData = New-Object -TypeName PSObject -Property $vuln

    # Set Custom Format for object
    $vulnData.PSObject.TypeNames.Insert(0,'Checklist.Item')

    Write-Output $vulnData

}
function Get-XCCDFFromArchive {
    [CmdletBinding()]
    param (

        # Specifies the STIG Zip file to use.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript( {
            if (-Not ($_ | Test-Path) ) {
                throw "Archive does not exist"
            }
            if ($_ -notmatch "\.zip") {
                throw "The file specified in the Path argument must be '.zip'"
            }
            return $true
        })]
        [Alias('FullName', 'PSPath')]
        [System.IO.FileInfo[]]
        $Path,

        # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default the search pulls all files that are like '_Manual-xccdf.xml', then the search is used to pull from just those files.
        [Parameter()]
        [String]
        $Search = '_Manual-xccdf.xml'

    )

    begin {
        Add-Type -AssemblyName System.IO.Compression.FileSystem
    }

    process {

        $Path | ForEach-Object {

            $archivePath = $_

            $zip = [System.IO.Compression.ZipFile]::OpenRead($archivePath.FullName)

            $zip.Entries | Where-Object { $_.Name -like "*_Manual-xccdf.xml" -and $_.Name -Match $Search } | ForEach-Object {

                try {
                    $stream = $_.Open()
                    $reader = New-Object IO.StreamReader($stream)
                    $xccdf = $reader.ReadToEnd()
                }
                catch {
                    $_
                }
                finally {
                    $reader.Close()
                    $stream.Close()
                }

                $fileData = [Ordered]@{
                    Name = $_.Name
                    Path = $archivePath.FullName
                    XML  = $xccdf
                }

                Write-Output (New-Object -TypeName PSObject -Property $fileData)
            }

            $zip.Dispose()

        }

    }

}
function New-ChecklistFileName {

    <#
    .SYNOPSIS
        Create a file name for STIG checklists

    .DESCRIPTION
        Create a file name for STIG checklists that include the Hostname, and STIG Name. For the Checklist specific for Websites, Databases, or Database instance will be added to the name as well.

    .EXAMPLE
        PS C:\> New-ChecklistFileName -ChecklistName "U_MS_Dot_Net_Framework_4-0_STIG_V1R9" -HostName "Computer1"

        Output: Computer1-U_MS_Dot_Net_Framework_4-0_STIG_V1R9.ckl

        Thie example demonstrates how to create a file name.

    #>


    [CmdletBinding(DefaultParameterSetName = "Host")]
    [OutputType([String])]
    param (

        # Specifies the default checklist name
        [Parameter(Mandatory)]
        [String]
        $ChecklistName,

        # Specifies the Host Name for the checklist.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Host")]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Website")]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "DBInstance")]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Database")]
        [string]
        $HostName,

        # Specifies the Host WebSite for the checklist.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Website")]
        [string]
        $WebSite,

        # Specifies the Host Database Instance for the checklist.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "DBInstance")]
        [string]
        $DatabaseInstance,

        # Specifies the Host Database for Checklist.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Database")]
        [string]
        $Database

    )

    process {

        $ckItem = $null

        switch ($PsCmdlet.ParameterSetName) {
            'Website' { $ckItem = $WebSite }
            'DBInstance' { $ckItem = $DatabaseInstance }
            'Database' { $ckItem = $Database }
        }

        if ($ckItem) {
            "{0}-{1}-{2}.ckl" -f $HostName, $ckItem, $ChecklistName
        }
        else {
            "{0}-{1}.ckl" -f $HostName, $ChecklistName
        }

    }

}
function Test-XMLFile {

    [CmdletBinding()]
    param (

        # Specifies the path to the xml file to be tested.
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.IO.FileInfo[]]
        $XmlPath,

        # Specifies the path to the xml schema file to use to validate the xml files.
        [Parameter(Mandatory)]
        [System.IO.FileInfo]
        $SchemaPath

    )

    begin {

        [Scriptblock] $ValidationEventHandler = {

            If ($_.Exception.LineNumber) {
                $Message = "$($_.Exception.Message) Line $($_.Exception.LineNumber), Position $($_.Exception.LinePosition)."
            }
            Else {
                $Message = ($_.Exception.Message)
            }

            $schemaErrors.Add([PSCustomObject]@{Message = $Message })
        }

    }

    process {

        $XmlPath | ForEach-Object {

            $xmlFile = $_

            $schemaErrors = New-Object System.Collections.Generic.List[System.Object]

            $ReaderSettings = New-Object -TypeName System.Xml.XmlReaderSettings
            $ReaderSettings.ValidationType = [System.Xml.ValidationType]::Schema
            $ReaderSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
            $ReaderSettings.Schemas.Add($null, $SchemaPath.FullName) | Out-Null
            $readerSettings.add_ValidationEventHandler($ValidationEventHandler)

            Try {

                $Reader = [System.Xml.XmlReader]::Create($xmlFile.FullName, $ReaderSettings)
                While ($Reader.Read()) {}
            }
            Catch {
                $schemaErrors.Add([PSCustomObject]@{ Message = ($_.Exception.Message) })
            }
            Finally {
                $Reader.Close()
            }

            $xmlOutput = [ordered]@{
                XMLFile = $xmlFile.Name
                Valid   = $true
            }

            If ($schemaErrors) {
                $xmlOutput.Valid = $false
                $xmlOutput.Add("Exceptions", $schemaErrors.Message)
            }

            Write-Output $(New-Object -TypeName PSObject -Property $xmlOutput)

        }

    }

}