module/RepoFunctions.ps1

function Initialize-StigRepo
{
    <#
 
    .SYNOPSIS
    Initalizes/builds the Stig Compliance Automation Repository (SCAR).
    Creates repository folders, installs latest dependent module versions, and generates StigData files required
    for SCAR functionality.
 
    .PARAMETER RootPath
    Path to the root of the SCAR repository/codebase.
 
    .EXAMPLE
    Build the STIG Compliance Automation Repository within a specified folderpath
    Initialize-StigRepo -RootPath "C:\StigRepo"
 
    #>


    [CmdletBinding()]
    param
    (
        [Parameter()]
        [string]
        $RootPath
    )

    Write-Output "Beginning Stig Compliance Automation Repository (SCAR) Build"

    if ('' -eq $RootPath)
    {
        $currentLocation = (Get-Location).Path
        $prompt = Read-Host -Prompt "`n`tThe `$RootPath Parameter was not provided.`n`tCurrent Location: $currentLocation`n`tDo you want to initialize the repository at your current location? Y/N"

        if ($prompt -like "y*")
        {
            $RootPath = (Get-Location).Path
        }
        else 
        {
            Write-Output "`nRepository Initialization Cancelled."
            return
        }
    }
    Write-Output "`tBuilding Repository Folder Structure"

    # Systems Folder
    $systemsPath    = New-Item -Path "$RootPath\Systems" -ItemType Directory -Force
    $stagingPath    = New-Item -Path "$SystemsPath\Staging" -ItemType Directory -Force

    # Configurations Folder
    $configPath     = New-Item -Path "$RootPath\Configurations" -ItemType Directory -Force

    # Artifacts Folder
    $artifactPath   = New-Item -Path "$RootPath\Artifacts" -ItemType Directory -Force
    $dscConfigPath  = New-Item -Path "$artifactPath\DscConfigs" -ItemType Directory -Force
    $mofPath        = New-Item -Path "$artifactPath\Mofs" -ItemType Directory -Force
    $CklPath        = New-Item -Path "$artifactPath\Stig Checklists" -ItemType Directory -Force

    # Resources Folder
    $resourcePath   = New-Item -Path "$RootPath\Resources" -ItemType Directory -Force
    $modulePath     = New-Item -Path "$resourcePath\Modules" -ItemType Directory -Force
    $stigDataPath   = New-Item -Path "$resourcePath\Stig Data" -ItemType Directory -Force
    $xccdfPath      = New-Item -Path "$stigDataPath\Xccdfs" -ItemType Directory -Force
    $orgSettingPath = New-Item -Path "$stigDataPath\Organizational Settings" -ItemType Directory -Force
    $mancheckPath   = New-Item -Path "$stigDataPath\Manual Checks" -ItemType Directory -Force
    $wikiPath       = New-Item -Path "$resourcePath\Wiki" -ItemType Directory -Force

    Write-Output "`tExtracting Resource Files"
    
    try
    {
        $moduleRoot = Split-Path -Path (Get-Module StigRepo).Path -Parent
        $configZip = "$moduleRoot\Resources\Configurations.zip"
        $wikiZip   = "$moduleRoot\Resources\wiki.zip"
        Expand-Archive $configZip -DestinationPath $RootPath -force
        Expand-Archive $wikiZip -DestinationPath $ResourcePath -force
    }
    catch
    {
        Write-Output "The StigRepo Module is not imported. Please re-import the module and try again."
        return
    }

    Update-StigRepo -RemoveBackup

    Write-Output "`n`tInstalling/Importing SCAR Modules"
    Sync-DscModules -LocalHost -Force
    Import-Module PowerSTIG -Force
    Import-Module StigRepo -Force

    Write-Output "STIG Compliance Automation Repository Build Complete."
    Write-Output "Run New-SystemData to begin System Data creation.`n`n"
}

function Update-StigRepo
{

    <#
 
    .SYNOPSIS
    Updates an existing/established Stig Compliance Automation Repository (SCAR).
    Downloads/installs the latest dependent module versions
    Creates a backup of existing StigData files and generates new files from the lastest version of the PowerSTIG module
 
    .PARAMETER RootPath
    Path to the root of the SCAR repository/codebase.
 
    .PARAMETER SkipStigRepoModule
    Skips downloading/updating the StigRepo module
 
    .PARAMETER SkipPowerStigModules
    Skips downloading/updating PowerSTIG and dependent modules
 
    .EXAMPLE
 
    Update the STIG Compliance Automation Repository in the current filepath
 
    Update-StigRepo
 
    .EXAMPLE
 
    Update the STIG Compliance Automation Repository within a specified folderpath
 
    Initialize-StigRepo -RootPath "C:\StigRepo"
 
    #>


    [CmdletBinding()]
    param
    (
        [Parameter()]
        [string]
        $RootPath = (Get-Location).Path,

        [Parameter()]
        [switch]
        $RemoveBackup,

        [Parameter()]
        [switch]
        $SkipStigRepoModule,

        [Parameter()]
        [switch]
        $SkipPowerSTIGModules

    )

    Write-Output "Starting SCAR Update"
    $stigDataPath   = (Resolve-Path -Path "$RootPath\Resources\Stig Data").Path
    $modulePath     = (Resolve-Path -Path "$RootPath\Resources\Modules" -erroraction Stop).Path
    $modules        = Get-Childitem $ModulePath

    # Update Dependent Modules
    Write-Output "`tRemoving old module dependencies"
    foreach ($module in $modules)
    {
        Write-Output "`t`tRemoving $($module.name)"
        Remove-Item $module.fullname -force -Recurse -Confirm:$false
    }

    Write-Output "`tInstalling new module Dependencies"

    if (-not ($SkipStigRepoModule))
    {
        Write-Output "`t`tUpdating StigRepo Module"
        try
        {
            if ($verbose)
            {
                Save-Module StigRepo -Path $ModulePath -Verbose
            }
            else 
            {
                Save-Module StigRepo -Path $ModulePath
            }
        }
        catch
        {
            Write-Host -ForegroundColor Red "`t`tUnable to install StigRepo Module from the PS Gallery"
            throw $_
        }
    }

    if (-not ($SkipPowerSTIGModules))
    {
        Write-Output "`t`tUpdating PowerSTIG Module and Dependencies"
        try
        {
            if ($verbose)
            {
                Save-Module PowerSTIG -Path $ModulePath -Verbose
            }
            else 
            {
                Save-Module PowerSTIG -Path $ModulePath
            }
        }
        catch
        {
            Write-Host -ForegroundColor Red "`t`tUnable to install StigRepo Module from the PS Gallery."
            throw $_
        }
    }

    #endregion Update Dependent Modules

    #region Update STIG Data Files

    Write-Output "`tUpdating STIG Data Files"

    # Backup STIG Data Folder
    Write-Output "`n`t`tBacking up current STIG Data"
    $resourcePath = Split-Path $StigDataPath -Parent
    $backupPath   = "$resourcePath\Stig Data-Backup"
    Copy-Item $stigDataPath -Destination $backupPath -Force -Recurse

    # Update Xccdfs
    Write-Output "`n`t`tUpdating STIG XCCDF Files"
    Get-Item "$StigDataPath\Xccdfs" | Remove-Item -Recurse -Force -Confirm:$false
    $currentXccdfFolders    = Get-Childitem "$StigDataPath-Backup\Xccdfs\*" -Directory
    $newXccdfPath           = New-Item -ItemType Directory -Path $StigDataPath -Name "Xccdfs" -Force -Confirm:$false
    $newXccdfFolders        = Get-Childitem "$ModulePath\PowerSTIG\*\StigData\Archive\*" -Directory
    $newXccdfFolders        | Copy-Item -Destination "$StigDataPath\Xccdfs" -Force -Recurse -Confirm:$false
    $customxccdfs           = $currentXccdfFolders | where { ((Compare-Object -ReferenceObject $currentXccdfFolders.name -DifferenceObject $newXccdfFolders.name).inputobject) -contains $_.name }
    $customXccdfs.FullName  | Copy-Item -Destination $newXccdfPath -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue

    # Update Org Settings
    Write-Output "`n`t`tUpdating Organizational Setting Files"
    Get-Item "$StigDataPath\Organizational Settings" | Remove-Item -Recurse -Force -Confirm:$false
    $currentOrgSettings = Get-Childitem "$StigDataPath-Backup\Organizational Settings\*org.default.xml"
    $null               = New-Item -ItemType Directory -Path $StigDataPath -Name "Organizational Settings" -Force -Confirm:$false
    $newOrgSettings     = Get-Childitem "$ModulePath\PowerSTIG\*\StigData\Processed\*.org.default.xml"
    $newOrgSettings | Copy-Item -Destination "$StigDataPath\Organizational Settings" -Force -Confirm:$false

    # Manual Checks
    Write-Output "`n`t`tUpdating Manual Check Files"

    $powerStigXccdfPath     = (Resolve-Path "$modulePath\PowerStig\*\StigData\Archive").Path
    $powerStigProcessedPath = (Resolve-Path "$ModulePath\PowerSTIG\*\StigData\Processed").Path
    $xccdfs                 = Get-Childitem "$powerStigXccdfPath\*.xml" -recurse
    $processedXccdfs        = Get-Childitem "$powerStigProcessedPath\*.xml" -recurse | where {$_.name -notlike "*org.default*"}
    $newManualCheckPath     = New-Item -ItemType Directory -Path $StigDataPath -Name "Manual Checks" -Force -Confirm:$False
    $oldManualCheckPath     = (Resolve-Path "$StigDataPath-Backup\Manual Checks").Path
    $currentManualChecks    = Get-ChildItem -Path $oldManualCheckPath
    $currentManualChecks | Copy-Item -Destination $StigDataPath -Force -Recurse -Confirm:$false

    foreach ($xccdf in $processedXccdfs)
    {

        switch -Wildcard ($xccdf.name)
        {
            "WindowsServer*2019*"   {$xccdfFolderName = "Windows.Server.2019"}
            "WindowsServer*2016*"   {$xccdfFolderName = "Windows.Server.2016"}
            "WindowsServer*2012*"   {$xccdfFolderName = "Windows.Server.2012R2"}
            "*Firewall*"            {$xccdfFolderName = "Windows.Firewall"}
            "*DNS*"                 {$xccdfFolderName = "Windows.DNS"}
            "*Defender*"            {$xccdfFolderName = "Windows.Defender"}
            "*Client*"              {$xccdfFolderName = "Windows.Client"}
            "*IISServer*"           {$xccdfFolderName = "WebServer"}
            "*IISSite*"             {$xccdfFolderName = "WebSite"}
            "*VSphere*"             {$xccdfFolderName = "VSphere"}
            "*SQL*Server*"          {$xccdfFolderName = "Sql Server"}
            "*Oracle*"              {$xccdfFolderName = "OracleJRE"}
            "*Office*"              {$xccdfFolderName = "Office"}
            "*McAfee*"              {$xccdfFolderName = "McAfee"}
            "*Ubuntu*"              {$xccdfFolderName = "Linux.Ubuntu"}
            "*RHEL*"                {$xccdfFolderName = "Linux.RHEL"}
            "*InternetExplorer*"    {$xccdfFolderName = "InternetExplorer"}
            "*Edge*"                {$xccdfFolderName = "Edge"}
            "*DotNet*"              {$xccdfFolderName = "DotNet"}
            "*Chrome*"              {$xccdfFolderName = "Chrome"}
            "*FireFox*"             {$xccdfFolderName = "FireFox"}
            "*Adobe*"               {$xccdfFolderName = "Adobe"}
        }

        [xml]$xccdfcontent  = Get-Content $xccdf.FullName -Encoding UTF8
        $manualRules        = $xccdfContent.DisaStig.ManualRule.Rule.id
        $manualCheckContent = New-Object System.Collections.ArrayList
        $manualCheckFolder  = "$StigDataPath\Manual Checks\$xccdfFolderName"
        $stigVersion = $xccdf.basename.split("-") | Select -last 1

        if (-not(Test-Path $manualCheckFolder))
        {
            $null = New-Item -ItemType Directory -Path $manualCheckFolder
        }
        $manualCheckFilePath = "$manualCheckFolder\$($xccdfContent.DisaStig.StigId)-$stigVersion-manualChecks.psd1"

        if ($null -ne $manualRules)
        {
            Write-Output "`t`t`tGenerating Manual Check file for $($xccdf.Name)"
            foreach ($vul in $manualRules)
            {
                $null = $manualCheckContent.add("@{")
                $null = $manualCheckContent.add(" VulID = `"$($vul)`"")
                $null = $manualCheckContent.add(" Status = `"NotReviewed`"")
                $null = $manualCheckContent.add(" Comments = `"EXAMPLE: This Comment was provided by the STIG Compliance Automation Repository. Modify $manualCheckFilePath to customize the status/comments for this finding.`"")
                $null = $manualCheckContent.add("}`n")
            }
            $manualCheckContent | Out-File $manualCheckFilePath -force
        }
        else
        {
            Write-Output "`t`t`tGenerating Manual Check file for $($xccdf.Name)"
            $null = $manualCheckContent.add("@{")
            $null = $manualCheckContent.add(" VulID = `"V-XXXX`"")
            $null = $manualCheckContent.add(" Status = `"NotReviewed`"")
            $null = $manualCheckContent.add(" Comments = `"EXAMPLE: This Comment was provided by the STIG Compliance Automation Repository. Modify $manualCheckFilePath to customize the status/comments for this finding.`"")
            $null = $manualCheckContent.add("}`n")
            $manualCheckContent | Out-File $manualCheckFilePath -force
        }
    }

    if ($RemoveBackup)
    {
        Get-Item "$resourcePath\Stig Data-Backup" | Remove-Item -Force -Recurse -Confirm:$false
    }
}
function Start-DscBuild
{
    <#
    .SYNOPSIS
    Executes SCAR functions that compile dynamic configurations for each machine based on the parameters and
    parameter values provided within that VM's configuration data.
 
    .PARAMETER Rootpath
    Path to the root of the SCAR repository/codebase.
 
    .PARAMETER SyncModules
    Executes the Sync-DscModules cmdlet and sync modules/versions with what is in the "5. resouces\Modules" folder of
    SCAR.
 
    .PARAMETER CompressArtifacts
    Switch parameter that archives the artifacts produced by SCAR. This switch compresses the artifacts and
    places them in the archive folder.
 
    .PARAMETER CleanBuild
    Switch parameter that removes files from the MOFs and Artifacts folders to create a clean slate for the SCAR build.
 
    .PARAMETER SystemFiles
    Allows users to provide an array of configdata files to target outside of the Systems folder.
 
    .PARAMETER ImportModules
    Imports Required/Dependent Modules - Modules must be synced for proper functionality.
 
    .EXAMPLE
    Start-DscBuild -RootPath "C:\DSC Management" -CleanBuild -PreRequisites
 
    #>


    [cmdletbinding()]
    param(

        [Parameter()]
        [string]
        $RootPath = (Get-Location).Path,

        [Parameter()]
        [string]
        $TargetFolder,

        [Parameter()]
        [string]
        $ComputerName,
        
        [Parameter()]
        [switch]
        $SyncModules,

        [Parameter()]
        [switch]
        $CompressArtifacts,

        [Parameter()]
        [switch]
        $CleanBuild,

        [Parameter()]
        [System.Collections.ArrayList]
        $SystemFiles,

        [Parameter()]
        [switch]
        $ImportModules
    )

    # Root Folder Paths
    try {
        $SystemsPath     = (Resolve-Path -Path "$RootPath\Systems" -ErrorAction Stop).Path
        $dscConfigPath   = (Resolve-Path -Path "$RootPath\Configurations" -ErrorAction Stop).Path
        $resourcePath    = (Resolve-Path -Path "$RootPath\Resources" -ErrorAction Stop).Path
        $artifactPath    = (Resolve-Path -Path "$RootPath\Artifacts" -ErrorAction Stop).Path
        $mofPath         = (Resolve-Path -Path "$RootPath\Artifacts\Mofs" -ErrorAction Stop).Path      
    }
    catch 
    {
        Write-Output "The provided RootPath is not not a Valid Stig Repository Location - $RootPath"
        return
    }

    # Begin Build
    Write-Output "Beginning Desired State Configuration Build Process`r`n"

    # Remove old Mofs/Artifacts
    if ($CleanBuild)
    {
        Remove-StigRepoData -RootPath $RootPath
    }

    # Validate Modules on host and target machines
    if ($SyncModules)
    {
        Sync-DscModules -Rootpath $RootPath
    }

    # Import required DSC Resource Module
    if ($ImportModules)
    {
        Import-DscModules -RootPath $RootPath
    }

    # Combine PSD1 Files
    $allNodesDataFile = "$artifactPath\DscConfigs\AllNodes.psd1"

    if ($SystemFiles.count -lt 1)
    {
        $SystemFiles = New-Object System.Collections.ArrayList
        if ('' -ne $ComputerName)
        {
            $null = Get-ChildItem -Path "$systemsPath\*.psd1" -Recurse | Where-Object { ($_.Fullname -NotLike "*Staging*") -and ($_.Fullname -Notlike "*Readme*") -and ($_.FullName -like "*$ComputerName*")} | ForEach-Object {$null = $Systemfiles.add($_)}
            Get-CombinedConfigs -RootPath $RootPath -AllNodesDataFile $allNodesDataFile -SystemFiles $SystemFiles
            Export-DynamicConfigs -SystemFiles $SystemFiles -ArtifactPath $artifactPath -DscConfigPath $dscConfigPath
            Export-Mofs -RootPath $RootPath -TargetFolder $TargetFolder
        }
        elseif ('' -eq $TargetFolder)
        {
            $null = Get-ChildItem -Path "$SystemsPath\*.psd1" -Recurse | Where-Object { ($_.Fullname -notmatch "Staging") -and ($_.Fullname -Notlike "Readme*")} | ForEach-Object {$null = $systemFiles.add($_)}
            Get-CombinedConfigs -RootPath $RootPath -AllNodesDataFile $allNodesDataFile -SystemFiles $SystemFiles
            Export-DynamicConfigs -SystemFiles $SystemFiles -ArtifactPath $artifactPath -DscConfigPath $dscConfigPath
            Export-Mofs -RootPath $RootPath
        }
        elseif ('' -ne $TargetFolder)
        {
            $null = Get-ChildItem -Path "$systemsPath\$TargetFolder\*.psd1" -Recurse | Where-Object { ($_.Fullname -notmatch "Staging") -and ($_.Fullname -Notlike "Readme*")} | ForEach-Object {$null = $Systemfiles.add($_)}
            Get-CombinedConfigs -RootPath $RootPath -AllNodesDataFile $allNodesDataFile -SystemFiles $SystemFiles -TargetFolder $TargetFolder
            Export-DynamicConfigs -SystemFiles $SystemFiles -ArtifactPath $artifactPath -DscConfigPath $dscConfigPath -TargetFolder $TargetFolder
            Export-Mofs -RootPath $RootPath -TargetFolder $TargetFolder
        }
    }
    else
    {
        Get-CombinedConfigs -RootPath $RootPath -AllNodesDataFile $allNodesDataFile -SystemFiles $SystemFiles
        Export-DynamicConfigs -SystemFiles $SystemFiles -ArtifactPath $artifactPath -DscConfigPath $dscConfigPath
        Export-Mofs -RootPath $RootPath
    }

    # Archive generated artifacts
    if ($CompressArtifacts)
    {
        Compress-DscArtifacts -Rootpath $RootPath
    }

    # DSC Build Complete
    Write-Output "`n`nDesired State Configuration Build complete.`n`n"
}

function Get-CombinedConfigs
{
    <#
 
    .SYNOPSIS
    Generates configuration data for each node defined within a targeted folder and generates a single .psd1 for each node with
    their combined "AppliedConfigurations" and parameters to generate MOFs off of. Also generates a single configuration data file
    containing all nodes/configurations.
 
    .PARAMETER RootPath
    Path to the Root of the SCAR platforms
 
    .EXAMPLE
    Get-CombinedConfigs -RootPath "C:\SCAR"
 
    #>


    [cmdletBinding()]
    param (

        [Parameter()]
        [string]
        $RootPath = (Get-Location).Path,

        [Parameter()]
        [string]
        $AllNodesDataFile,

        [Parameter()]
        [array]
        $TargetFolder,

        [Parameter()]
        [System.Collections.ArrayList]
        $SystemFiles

    )

    try
    {
        $systemsPath    = Resolve-Path -Path "$Rootpath\*Systems"
        $artifactPath   = (Resolve-Path -Path "$Rootpath\*Artifacts" -ErrorAction Stop).Path
        if ("" -eq $AllNodesDataFile) { $AllNodesDataFile = "$artifactPath\DscConfigs\AllNodes.psd1" }
    }
    catch
    {
        Write-Output "$RootPath is not a valid repository."
        break
    }

    Write-Output "`tBeginning System Data Processing"
    if ($null -eq $SystemFiles)
    {
        if ('' -ne $targetFolder)
        {
            $systemFiles = Get-ChildItem -Path "$SystemsPath\*.psd1" -Recurse | Where-Object { ($_.Fullname -notmatch "Staging") -and ($_.Fullname -Notlike "Readme*") }
        }
        else
        {
            $systemFiles = Get-ChildItem -Path "$SystemsPath\$TargetFolder\*.psd1" -Recurse | Where-Object { ($_.Fullname -notmatch "Staging") -and ($_.Fullname -Notlike "Readme*") }
        }
    }

    foreach ($systemFile in $systemFiles)
    {
        $data = Invoke-Expression (Get-Content $systemFile.FullName | Out-String)

        if ($null -ne $data.AppliedConfigurations)
        {
            [array]$buildFiles += $systemFile
        }
    }

    if ($buildFiles.count -lt 1)
    {
        Write-Output "`t`tNo DSC configdata files were provided."
        break
    }
    else
    {
        New-Item -Path $AllNodesDataFile -ItemType File -Force | Out-Null
        $configCount = $buildFiles.count
        $iteration = 0
        "@{`n`tAllNodes = @(`n" | Out-File $AllNodesDataFile -Encoding utf8

        foreach ($buildFile in $buildFiles)
        {
            Write-Output "`t`t$($buildFile.BaseName) - Processing System Data"
            $iteration++
            $configDataContent = Get-Content -Path $buildFile.FullName -encoding UTF8
            $configDataContent | ForEach-Object -Process {
                "`t`t" + $_ | Out-file $AllNodesDataFile -Append -Encoding utf8
            }

            if (($iteration -ne $configCount) -and ($counfigCount -ne 1))
            {
                "`t`t," | Out-file $allNodesDataFile -Append -Encoding utf8
            }
        }
        "`t)`n}" | Out-File $allNodesDataFile -Append -Encoding utf8
    }
}

function Export-DynamicConfigs
{
    <#
 
    .SYNOPSIS
    Generates DSC scripts with combined parameter and parameter values based on
    provided configuration data.
 
    .PARAMETER SystemFiles
    Array of configuration data files. Targets all .psd1 files under the "Systems" folder
    that are not located in the staging folder.
    Example -SystemFiles $ConfigDataArray
 
    .PARAMETER ArtifactPath
    Path to the Artifacts Folder. Defaults to the "4. Artifacts" folder from the Rootpath provided by
    the Start-DscBuild function.
 
    .EXAMPLE
    Export-DynamicConfigs -SystemFiles $SystemFiles -ArtifactPath $artifactPath
 
    #>


    [cmdletBinding()]
    param (

        [Parameter()]
        [System.Collections.ArrayList]
        $SystemFiles,

        [Parameter()]
        [string]
        $TargetFolder,

        [Parameter()]
        [string]
        $ArtifactPath,

        [Parameter()]
        [string]
        $DscConfigPath
    )

    Write-Output "`n`tStarting DSC Compilation"
    $jobs = New-Object System.Collections.ArrayList
    foreach ($SystemFile in $SystemFiles)
    {
        $machinename = $SystemFile.basename
        if ('' -ne $TargetFolder -and $SystemFile.fullname -notlike "*\$TargetFolder\*")
        {
            Continue
        }
        else
        {
            Write-Output "`t`t$($SystemFile.basename) - Starting DSC Compilation Job"

            $job = Start-Job -Scriptblock {
                try
                {
                    $SystemFile       = $using:SystemFile
                    $dscConfigPath      = $using:dscConfigPath
                    $artifactPath       = $using:ArtifactPath
                    $machinename        = $using:machinename
                    $nodeConfigScript   = "$ArtifactPath\DscConfigs\$machineName.ps1"
                    $data               = Invoke-Expression (Get-Content $SystemFile.FullName | Out-String)

                    if ($null -ne $data.AppliedConfigurations)
                    {
                        $appliedConfigs     = $data.appliedconfigurations.Keys
                        $lcmConfig          = $data.LocalConfigurationManager.Keys
                        $nodeName           = $data.NodeName
                        New-Item -ItemType File -Path $nodeConfigScript -Force | Out-Null

                        foreach ($appliedConfig in $appliedConfigs)
                        {

                            if (Test-Path $SystemFile.fullname)
                            {
                                Write-Output "`t`tConfigData Import - $appliedConfig"

                                $dscConfigScript = "$DscConfigPath\$appliedConfig.ps1"
                                $fileContent = Get-Content -Path $dscConfigScript -Encoding UTF8 -ErrorAction Stop
                                $fileContent | Out-file $nodeConfigScript -Append -Encoding utf8 -ErrorAction Stop
                                . $dscConfigScript
                                Invoke-Expression ($fileContent | Out-String) #DevSkim: ignore DS104456
                            }
                            else
                            {
                                Throw "The configuration $appliedConfig was specified in the $($SystemFile.fullname) file but no configuration file with the name $appliedConfig was found in the \Configurations folder."
                            }
                        }
                        $mainConfig = New-Object System.Collections.ArrayList
                        $null = $mainConfig.add("Configuration MainConfig`n{`n`tNode `$AllNodes.Where{`$_.NodeName -eq `"$nodeName`"}.NodeName`n`t{")

                        foreach ($appliedConfig in $appliedConfigs)
                        {
                            Write-Output "`t`tParameter Import - $AppliedConfig"

                            $syntax                     = Get-Command $appliedConfig -Syntax -ErrorAction Stop
                            $appliedConfigParameters    = [Regex]::Matches($syntax, "\[{1,2}\-[a-zA-Z0-9]+") |
                            Select-Object @{l = "Name"; e = { $_.Value.Substring($_.Value.IndexOf('-') + 1) } },
                            @{l = "Mandatory"; e = { if ($_.Value.IndexOf('-') -eq 1) { $true }else { $false } } }
                            $null = $mainconfig.add("`n`t`t$appliedConfig $appliedConfig`n`t`t{`n")

                            foreach ($appliedConfigParameter in $appliedConfigParameters)
                            {
                                if ($null -ne $data.appliedconfigurations.$appliedConfig[$appliedConfigParameter.name])
                                {
                                    $null = $mainConfig.add("`t`t`t$($appliedConfigParameter.name) = `$node.appliedconfigurations.$appliedConfig[`"$($appliedConfigParameter.name)`"]`n")
                                }
                                elseif ($true -eq $appliedConfigParameter.mandatory)
                                {
                                    $errorMessage = New-Object System.Collections.ArrayList
                                    $null = $errorMessage.add("$nodeName configuration $appliedConfig has a mandatory parameter $($appliedConfigParameter.name) and was not specified.`n`n")
                                    $null = $errorMessage.add("$appliedConfig = @{`n")
                                    foreach ($appliedConfigParameter in $appliedConfigParameters)
                                    {
                                        $null = $errorMessage.add("`t$($appliedconfigParameter.name) = `"VALUE`"`n")
                                    }
                                    $null = $errorMessage.add("}")
                                    Throw $errorMessage
                                }
                            }
                            $null = $mainConfig.add("`t`t}`n")
                        }
                        $null = $mainConfig.add("`t}`n}`n")
                        $mainConfig | Out-file $nodeConfigScript -nonewline -Append -Encoding utf8
                        #endregion Build configurations and generate MOFs

                        #region Generate data for meta.mof (Local Configuration Manager)

                        if ($null -ne $lcmConfig)
                        {
                            Write-Output "`t`tGenerating LCM Configuration"
                            [array]$lcmParameters = "ActionAfterReboot", "AllowModuleOverWrite", "CertificateID", "ConfigurationDownloadManagers", "ConfigurationID", "ConfigurationMode", "ConfigurationModeFrequencyMins", "DebugMode", "StatusRetentionTimeInDays", "SignatureValidationPolicy", "SignatureValidations", "MaximumDownloadSizeMB", "PartialConfigurations", "RebootNodeIfNeeded", "RefreshFrequencyMins", "RefreshMode", "ReportManagers", "ResourceModuleManagers"
                            $localConfig = New-Object System.Collections.ArrayList
                            $null = $localConfig.add("[DscLocalConfigurationManager()]`n")
                            $null = $localConfig.add("Configuration LocalConfigurationManager`n{`n`tNode `$AllNodes.Where{`$_.NodeName -eq `"$nodeName`"}.NodeName`n`t{`n`t`tSettings {`n")

                            foreach ($setting in $lcmConfig)
                            {
                                if ($null -ne ($lcmParameters | Where-Object { $setting -match $_ }))
                                {
                                    $null = $localConfig.add("`t`t`t$setting = `$Node.LocalconfigurationManager.$Setting`n")
                                }
                                else
                                {
                                    Write-Warning "The term `"$setting`" is not a configurable setting within the Local Configuration Manager."
                                }
                            }
                            $null = $localConfig.add("`t`t}`n`t}`n}")
                            $localConfig | Out-file $nodeConfigScript -nonewline -Append -Encoding utf8
                        }
                    }
                    Write-Output "`t$nodeName configuration file successfully generated.`r`n"
                }
                catch
                {
                    Write-Warning "`t$nodeName configuration file failed.`r`n"
                    throw $_
                }
            }
        }
        $null = $jobs.add($job.Id)
    }
    Write-Output "`n`tDSC Compilation jobs started. Checking status every 30 seconds and output will be displayed once complete."
    do
    {
        $completedJobs  = (Get-Job -ID $jobs | where {$_.state -ne "Running"}).count
        $runningjobs    = (Get-Job -ID $jobs | where {$_.state -eq "Running"}).count
        Write-Output "`t`tCompilation Job Status:`t$runningJobs Jobs Currently Processing`t$completedJobs/$($jobs.count) Jobs Completed"
        Start-Sleep -Seconds 30
    }
    while ((Get-Job -ID $jobs).State -contains "Running")
    Write-Output "`n`t`tCompilation jobs completed. Receiving job output"
    Get-Job -ID $jobs | Wait-Job | Receive-Job
}

function Export-Mofs
{
    <#
 
    .SYNOPSIS
 
    .PARAMETER Rootpath
    Path to the root of the SCAR repository/codebase.
 
    .EXAMPLE
    Export-Mofs -RootPath "C:\SCAR"
 
    #>


    [cmdletbinding()]
    param(

        [Parameter()]
        [string]
        $RootPath = (Get-Location).Path,

        [Parameter()]
        [string]
        $TargetFolder,

        [Parameter()]
        [System.Collections.ArrayList]
        $SystemFiles

    )

    Write-Output "`tStarting jobs to generate Managed Object Files."
    $mofPath            = (Resolve-Path -Path "$RootPath\*Artifacts\Mofs").Path
    $dscConfigPath      = (Resolve-Path -Path "$RootPath\*Artifacts\DscConfigs").Path
    $allNodesDataFile   = (Resolve-Path -Path "$dscConfigPath\Allnodes.psd1").path
    $dscNodeConfigs     = New-Object System.Collections.ArrayList
    $jobs               = New-Object System.Collections.ArrayList

    if ($SystemFiles.count -lt 1)
    {
        $SystemFiles        = New-Object System.Collections.ArrayList

        if ('' -ne $TargetFolder)
        {
            $null = Get-Childitem "$RootPath\Systems\$TargetFolder\*.psd1" -Recurse | Where-Object fullname -notlike "*staging*" | ForEach-Object {$null = $SystemFiles.add($_)}
        }
        else
        {
            $null = Get-Childitem "$RootPath\Systems\*.psd1" -Recurse | Where-Object fullname -notlike "*staging*" | ForEach-Object {$null = $SystemFiles.add($_)}
        }
    }

    foreach ($file in $SystemFiles)
    {

        $basename = $file.basename
        if (Test-Path "$dscConfigPath\$basename.ps1")
        {
            $null = Get-Item -path "$dscConfigPath\$basename.ps1" -erroraction SilentlyContinue | ForEach-Object {$null = $dscNodeConfigs.add($_)}
        }
        else
        {
            Write-Warning "No DSC Configuration script exists for $basename."
            continue
        }
    }

    foreach ($nodeConfig in $DscNodeConfigs)
    {
        $nodeName       = $nodeConfig.BaseName
        $configPath     = $nodeConfig.FullName
        $systemFile     = (Resolve-Path -Path "$RootPath\Systems\*\$nodeName.psd1").Path
        $data           = Invoke-Expression (Get-Content $SystemFile | Out-String)

        if ($null -ne $data.AppliedConfigurations)
        {
            Write-Output "`t`t$($nodeConfig.BaseName) - Generating Managed Object File(s)"

            try
            {

                $job = Start-Job -Scriptblock {

                    $nodeConfig         = $using:nodeConfig
                    $nodeName           = $using:nodeName
                    $allNodesDataFile   = $using:allNodesDataFile
                    $mofPath            = $using:mofPath
                    $configPath         = $using:ConfigPath
                    $data               = $using:data
                    # Execute each file into memory
                    . "$configPath"

                    # Execute each configuration with the corresponding data file
                    try
                    {
                        $null = MainConfig -ConfigurationData $allNodesDataFile -OutputPath $mofPath -ErrorAction Stop 3> $null
                        Write-Output "`t`tMOF Generated for $nodeName"

                    }
                    catch
                    {
                        Write-Output "MOF Generation Failed for $nodeName"
                    }
                    # Execute each Meta Configuration with the corresponding data file
                    if ($null -ne $data.LocalConfigurationManager)
                    {
                        try
                        {
                            $null = LocalConfigurationManager -ConfigurationData $allNodesDataFile -Outputpath $mofPath -Erroraction Stop 3> $null
                            Write-Output "`t`tLocalConfigurationManager MOF Generated for $nodeName"
                        }
                        catch
                        {
                            Write-Output "LocalConfigurationManager Generation Failed for $nodeName"
                        }
                    }
                }
                $null = $jobs.add($job.id)
            }
            catch
            {
                Throw "Error occured executing $SystemFile to generate MOF.`n $($_)"
            }
        }
    }
    Write-Output "`n`tMOF Export Jobs are currently running. Checking status every 30 seconds and output will be displayed once complete."
    do
    {
        $completedJobs  = (Get-Job -ID $jobs | where {$_.state -ne "Running"}).count
        $runningjobs    = (Get-Job -ID $jobs | where {$_.state -eq "Running"}).count
        Write-Output "`t`tMOF export Job Status:`t$runningJobs Jobs Currently Processing`t$completedJobs/$($jobs.count) Jobs Completed"
        Start-Sleep -Seconds 30
    }
    while ((Get-Job -ID $jobs).State -contains "Running")
    Write-Output "`n`t`t$($jobs.count) Mof Export Jobs completed. Receiving job output"
    Get-Job -ID $jobs | Wait-Job | Receive-Job
}

function Remove-StigRepoData
{
    <#
 
    .SYNOPSIS
    Removes all existing artifacts the SCAR repository including System data, Compiled DSC Scripts, MOFs, and STIG Checklists
    All files removed from the following locations:
        - Systems
        - Artifacts\Mofs
        - Artifacts\DscConfigs
        - Artifacts\STIG Checklists
 
    .PARAMETER Rootpath
    Path to the root of the SCAR repository/codebase.
 
    .EXAMPLE
    Remove-StigRepoData -Rootpath "C:\SCAR"
 
    #>


    [cmdletBinding()]
    param (

        [Parameter()]
        [string]
        $RootPath = (Get-Location).Path

    )

    Write-Host "Starting StigRepo Cleanup"

    try
    {
        $artifactPath   = (Resolve-Path "$RootPath\Artifacts" -ErrorAction 'Stop').Path
        $mofPath        = (Resolve-Path "$ArtifactPath\Mofs" -ErrorAction 'Stop').Path
        $cklPath        = (Resolve-Path "$ArtifactPath\STIG Checklists" -ErrorAction 'Stop').Path
        $dscConfigPath  = (Resolve-Path "$ArtifactPath\DscConfigs" -ErrorAction 'Stop').Path
        $systemsPath    = (Resolve-Path "$RootPath\Systems" -ErrorAction 'Stop').Path
    }
    catch
    {
        Write-Output "`t$RootPath is not a valid Stig Compliance Automation Repository."
        return
    }

    Write-Output "`tGathing StigRepo Files to remove"
    $mofs           = Get-Childitem -Path "$MofPath\*.mof" -Recurse
    $dscConfigs     = Get-Childitem -Path "$dscConfigPath\*.ps1" -Recurse
    $checklists     = Get-Childitem -Path "$cklPath\*.ckl" -Recurse
    $SystemFiles    = Get-Childitem -Path "$SystemsPath\" -Recurse | Where {$_.name -notlike "*.keep*"}

    Write-Output "`tAdding Files to removal array."
    $removeItems = New-Item System.Collections.Arraylist
    $mofs        | ForEach-Object { $removeItems.Add($_.FullName) }
    $dscConfigs  | ForEach-Object { $removeItems.Add($_.FullName) }
    $checklists  | ForEach-Object { $removeItems.Add($_.FullName) }
    $systemFiles | ForEach-Object { $removeItems.Add($_.FullName) }

    Write-Output "`tRemoving files from the Stig Complaince Automation Repository."

    foreach ($item in $removeItems)
    {
        Write-Output "`t`tRemoving $($item.Name)"
        Remove-Item $item.Fullname -Confirm:$false -ErrorAction SilentlyContinue
    }
    Write-Output "`r`nStig Repo cleanup complete."
}

function Compress-StigRepoArtifacts
{
    <#
 
    .SYNOPSIS
    Compresses the artifacts folder of the StigRepo. Resulting Zip file will also be stored in artifacts.
 
    .PARAMETER RootPath
    Path to the root of the SCAR repository/codebase.
 
    .EXAMPLE
 
    Compress-StigRepoArtifacts -RootPath "C:\StigRepo"
    #>


    [cmdletbinding()]
    param(

        [Parameter()]
        [String]
        $RootPath = (Get-Location).Path

    )

    # Archive the current MOF and build files in MMddyyyy_HHmm_DSC folder format
    $artifactPath   = (Resolve-Path -Path "$Rootpath\*Artifacts").Path
    $dateStamp      = (Get-Date -format "ddMMMyyyy")

    Compress-Archive -Path $artifactPath -DestinationPath "$artifactPath\RepoArtifacts-$dateStamp.zip" -Update
}

function Import-DscModules
{
    <#
 
    .SYNOPSIS
    Imports the required modules stored in the "Resources\Modules" folder on the local system.
 
    .PARAMETER RootPath
    Path to the root of the SCAR repository/codebase.
 
    .EXAMPLE
    Import-DscModules -ModulePath "$RootPath\Resouces\Modules"
 
    #>


    [cmdletbinding()]
    param(

        [Parameter()]
        [String]
        $RootPath

    )

    try
    {
        $modulePath = (Resolve-Path -Path "$RootPath\Resources\Modules" -ErrorAction 'Stop')
    }
    catch
    {
        Write-Output "`t$RootPath is not a valid STIG Complaince Automation Repository"
        return
    }

    $modules = @(Get-ChildItem -Path $ModulePath -Directory -Depth 0)
    Write-Output "`n`tBUILD: Importing required modules onto the local system."

    foreach ($module in $modules)
    {
        Write-Output "`t`tImporting Module - $($module.name)."
        $null = Import-Module $Module.name -Force
    }

    $null = Set-PowerCLIConfiguration -Scope AllUsers -ParticipateInCEIP $false -Confirm:$false
    Write-Output "`n"
}