BaselineManagement.psm1

$Helpers = "$PsScriptRoot\Helpers\"
$Parsers = "$PsScriptRoot\Parsers\"

Get-ChildItem -Path $Helpers -Recurse -Filter '*.ps1' | ForEach-Object { . $_.FullName }
Get-ChildItem -Path $Parsers -Recurse -Filter '*.ps1' | ForEach-Object { . $_.FullName }
<#
.Synopsis
   This is a universal ConvertTo-DSC function that converts GPO, SCMxml or SCMjson to DSC Configuration script.
.DESCRIPTION
   We utilize the type of object passed in to determine which function to call. Directories are associated with GPO backups, Objects are associated with GPO objects or JSON objects and XML with XML.
.EXAMPLE
   dir .\<GPO Backup GUID> | ConvertTo-DSC
.EXAMPLE
   dir .\scm.xml | ConvertTo-DSC
.EXAMPLE
   dir .\scm.json | ConvertTo-DSC
.EXAMPLE
   Backup-GPO <GPO Name> | ConvertTo-DSC
.INPUd
   Any supported baseline to be converted into DSC.
.OUTPUTS
   Output will come from calling cmdlet ConvertFrom-GPO, ConvertFrom-SCM, and ConvertFrom-ASC.
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function ConvertTo-DSC
{
    [CmdletBinding()]
    param
    (
        # This is the Path to either a SCM/JSON file or a GPO Backup directory.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName="Path")]
        [ValidateScript({Test-Path $_})]
        [Alias("BackupDirectory")]
        [string]$Path,

        # This is either a GPO Backup object or a JSON/XML object.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName="Object")]
        [Psobject]$Object,

        # This is the hard coded stage that you can just force it into the type of input.
        [Parameter()]
        [ValidateSet("ADMX", "GPO", "SCMxml", "ASCjson")]
        [string]$Type,
        
        # Output Path that will default to an Output directory under the current Path.
        [ValidateScript({Test-Path $_})]
        [string]$OutputPath = $(Join-Path $pwd.Path "Output"),

        # ComputerName for Node processing.
        [string]$ComputerName = "localhost",

        # This determines whether or not to output a ConfigurationScript in addition to the localhost.mof
        [switch]$OutputConfigurationScript
    )
        
    Process
    {
        $Parameter = $null
        # Determine the Type of input we are dealing with.
        switch($Type)
        {
            Default
            {
                Write-Verbose "Trying to determine type of input from ParameterSet $($PSCmdlet.ParameterSetName)"
                switch ($PSCmdlet.ParameterSetName)
                {
                    "Path"
                    {
                        $item = Get-Item -Path $Path
                        if ($item -is [System.IO.DirectoryInfo])
                        {
                            Write-Verbose "Assuming GPO since Directory was given for Path"
                            $Type = "GPO"
                        }
                        else
                        {
                            switch ($item.Extension)
                            {
                                ".json" { $Type = "ASCjson" }
                                ".xml" { $Type = "SCMxml" }
                            }
                        }    
                        
                        $parameter = $item
                    }

                    "Object"
                    {
                        switch ($Object)
                        {
                            {$_ -is [XML]} { $Type = "SCMxml" }
                            {$_ -is [Microsoft.GroupPolicy.GpoBackup]} { $Type = "GPO" } 
                            {$_ -is [PSCustomObject]} { Write-Verbose "Assuming JSON if object is a custom Object"; $Type = "SCMjson"}
                        }

                        $Parameter = $object
                    }
                }
            }
        }

        # Call the appropriate conversion function based on input.
        $scriptblock = [scriptblock]::Create('param($param) ConvertFrom-' + $Type + ' @param')
        # Pass our parameters (minus the Type) to our Conversion cmdlet.
        $PSBoundParameters.Remove("Type") | Out-Null
        return Invoke-Command -ScriptBlock $scriptblock -ArgumentList $PSBoundParameters
    }
}

<#
.Synopsis
   This cmdlet converts Backed Up GPOs into DSC Configuration Scripts.
.DESCRIPTION
   This cmdlet will take the exported GPO and convert all internal settings into DSC Configurations.
.EXAMPLE
   Backup-GPO <GPO Name> | ConvertFrom-GPO
.EXAMPLE
   dir .\<GPO GUID> | ConvertFrom-GPO
.INPUTS
   The GPO Object or directory.
.OUTPUTS
   This script will output a localhost.mof if successful and a failed Configuration Script file if failed. It will also, on request, output the Configuration Script PS1 file.
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function ConvertFrom-GPO
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        # This is the Path of the GPO Backup Directory.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [String]$Path,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "LiteralPath")]
        [Alias('PSPath')]
        [String]$LiteralPath,

        # Output Path that will default to an Output directory under the current Path.
        [string]$OutputPath = $(Join-Path $pwd.Path "Output"),

        # ComputerName for Node processing.
        [string]$ComputerName = "localhost",

        # This determines whether or not to output a ConfigurationScript in addition to the localhost.mof
        [switch]$OutputConfigurationScript,

        [switch]$ShowPesterOutput
    )
    
    Begin
    {
        # These are the DSC Resources needed for NON-preference based GPOS.
        $NeededModules = 'PSDesiredStateConfiguration', 'AuditPolicyDSC', 'SecurityPolicyDSC', 'PowerShellAccessControl'

        # Start tracking Processing History.
        Clear-ProcessingHistory
        
        # Create the Configuration String
        $ConfigString = Write-DSCString -Configuration -Name "DSCFromGPO"
        # Add any resources
        $AddedResources = $false
        $ConfigString += Write-DSCString -ModuleImport -ModuleName $NeededModules
        # Add Node Data
        $configString += Write-DSCString -Node -Name $ComputerName
    }

    Process
    {
        $resolvedPath = $null
        if ($PSCmdlet.ParameterSetName -eq 'Path')
        {
            Try
            {
                $resolvedPath = $Path | Get-Item
            }
            Catch
            {
                Write-Error $_
                return
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath')
        {
            Try
            {
                $resolvedPath = $LiteralPath | Get-Item
            }
            Catch
            {
                Write-Error $_
                return
            }
        }

        Write-Host "Gathering GPO Data from $resolvedPath"
        $polFiles = Get-ChildItem -Path $Path -Filter registry.pol -Recurse

        $AuditCSVs = Get-ChildItem -Path $Path -Filter Audit.csv -Recurse

        $GPTemplateINFs = Get-ChildItem -Path $Path -Filter GptTmpl.inf -Recurse
            
        $PreferencesDirectory = Get-ChildItem -Path $Path -Directory -Filter "Preferences" -Recurse

        $AddingModules = @()
        if ($PreferencesDirectory -ne $null)
        {
            $PreferencesXMLs = Get-ChildItem -Path $PreferencesDirectory.FullName -Filter *.xml -Recurse

            if ($PreferencesXMLs -ne $null)
            {
                # These are the Preference Based DSC Resources.
                $AddingModules = 'xSMBShare', 'DSCR_PowerPlan', 'xScheduledTask', 'Carbon', 'PrinterManagement', 'rsInternationalSettings'
            }
        }
        
        if ($AddingModules.Count -gt 0 -and $AddedModules -eq $false)
        {
            $AddingModulesString = Write-DSCString -ModuleImport -ModuleName $AddingModules -AddingModules
            $ConfigString = $ConfigString.Insert($ConfigString.IndexOf("Node") - 2, "`n`t" + $AddingModulesString)
            $AddedModules = $true
        }

        # Loop through each Pol file.
        foreach ($polFile in $polFiles)
        {
            if ((Get-Command "Read-PolFile" -ErrorAction SilentlyContinue) -ne $null)
            {
                # Reaad each POL file found.
                Write-Verbose "Reading Pol File ($($polFile.FullName))"
                Try
                {
                    $registryPolicies = Read-PolFile -Path $polFile.FullName
                }
                Catch
                {
                    Write-Error $_
                }
            }
            elseif ((Get-Command "Parse-PolFile" -ErrorAction SilentlyContinue) -ne $null)
            {
                # Reaad each POL file found.
                Write-Verbose "Reading Pol File ($($polFile.FullName))"
                Try
                {
                    $registryPolicies = Parse-PolFile -Path $polFile.FullName
                }
                catch
                {
                    Write-Error $_ 
                }
            }
            else
            {
                Write-Error "Cannot Parse Pol files! Please download and install GPRegistryPolicyParser from github here: https://github.com/PowerShell/GPRegistryPolicyParser"
                break
            }

            # Loop through every policy in the Pol File.
            Foreach ($Policy in $registryPolicies)
            {
                $Hive = @{User = "HKCU"; Machine = "HKLM"}
                
                # Convert each Policy Registry object into a Resource Block and add it to our Configuration string.
                $ConfigString += Write-GPORegistryPOLData -Data $Policy -Hive $Hive[$polFile.Directory.BaseName]
            }
        }
            
        # Loop through each Audit CSV in the GPO Directory.
        foreach ($AuditCSV in $AuditCSVs)
        {
            $otherSettingsCSV = @()
            foreach ($CSV in (Import-CSV -Path $AuditCSV.FullName))
            {
                switch ($CSV)
                {
                    {$_.Subcategory -match "GlobalSacl"}
                    {
                        $otherSettingsCSV += $CSV
                        break
                    }
                        
                    {!([string]::IsNullOrEmpty($_.'Subcategory GUID'))}
                    {
                        $ConfigString += Write-GPOAuditCSVData -Entry $CSV
                        break
                    }    

                    {$_.Subcategory -match "^Option"}
                    {
                        $ConfigString += Write-GPOAuditOptionCSVData -Entry $CSV
                        break
                    }
                    Default 
                    {
                        Write-Warning  "ConvertFrom-GPO: $($CSV.SubCategory) is not currently supported"
                    }
                }   
            }

            <# Still trying to figure out how to handle Resource SACLS
            if ($othersettingsCSV.count -gt 0)
            {
                $contents = ""
                for ($i = 0; $i -lt $othersettingsCSV.Count; $i++)
                {
                    $setting = $othersettingsCSV[$i]
                    $contents += "$($setting.'Machine Name'),$($setting.'Policy Target'),$($setting.Subcategory),$($setting.'Subcategory GUID'),$($setting.'Inclusion Setting'),$($setting.'Exclusion Setting'),$($setting.'Setting Value')"
                    if (($i + 1) -lt $othersettingsCSV.Count)
                    {
                        $contents += ","
                    }
                    $contents += "`n"
                }
                 
                $tempCSVPath = "C:\windows\temp\polaudit.csv"
                $ConfigString += Write-DscString -Resource -Name "OtherAuditSettingsCSV" -Type File -Parameters @{DestinationPath=$tempCSVPath;Force=$true;Contents=$contents}
                $ConfigString += Write-DscString -Resource -Name "AuditPolicyDSC: Other Audit Settings" -Type AuditPolicyCSV -Parameters @{IsSingleInstance = "Yes"; CsvPath = $tempCSVPath; DependsOn = "[File]OtherAuditSettingsCSV"}
            }
            #>

        }

        # Loop through all the GPTemplate files.
        foreach ($GPTemplateINF in $GPTemplateINFs)
        {
            Write-Verbose "Reading GPTmp.inf ($($GPTemplateINF.FullName))" 
            # GPTemp files are in INI format so this function converts it to a hashtable.
            $ini = Get-IniContent $GPTemplateINF.fullname

            # Loop through every heading.
            foreach ($key in $ini.Keys)
            {
                # Loop through every setting in the heading.
                foreach ($subKey in $ini[$key].Keys)
                {
                    switch -regex ($key)
                    {
                        "Service General Setting"
                        {
                            $ConfigString += Write-GPOServiceINFData -Service $subkey -ServiceData $ini[$key][$subKey]
                        }

                        "Registry Values"
                        {
                            $ConfigString += Write-GPORegistryINFData -Key $subkey -ValueData $ini[$key][$subKey]
                        }

                        "File Security"
                        {
                            $ConfigString += Write-GPOFileSecurityINFData -Path $subkey -ACLData $ini[$key][$subKey]
                        }
                    
                        "Privilege Rights"
                        {
                            $ConfigString += Write-GPOPrivilegeINFData -Privilege $subkey -PrivilegeData $ini[$key][$subKey]
                        }
                    
                        "Kerberos Policy"
                        {
                            if ($GlobalConflictEngine.ContainsKey("SecurityOption"))
                            {
                                $ConfigString += Write-GPONewSecuritySettingINFData -Key $subKey -SecurityData $ini[$key][$subkey]
                            }
                            else
                            {
                                $ConfigString += Write-GPOSecuritySettingINFData -Key $subKey -SecurityData $ini[$key][$subkey]
                            }
                        }
                    
                        "Registry Keys"
                        {
                            $ConfigString += Write-GPORegistryACLINFData -Path $subkey -ACLData $ini[$key][$subKey]
                        }
                    
                        "System Access"
                        {
                            if ($GlobalConflictEngine.ContainsKey("SecurityOption"))
                            {
                                $ConfigString += Write-GPONewSecuritySettingINFData -Key $subKey -SecurityData $ini[$key][$subkey]
                            }
                            else
                            {
                                $ConfigString += Write-GPOSecuritySettingINFData -Key $subKey -SecurityData $ini[$key][$subkey]
                            }
                        }

                        "Event Audit"
                        {
                            $ConfigString += Write-GPOAuditINFData -Key $subKey -AuditData $ini[$key][$subkey]
                        }

                        "(Version|signature|Unicode|Group Membership)"
                        {

                        }
                        
                        Default
                        {
                            Write-Warning "ConvertFrom-GPO:GPTemp.inf $key AND $subkey heading not yet supported"
                        }
                    }
                }
            }

            # This has to be done separately because it can cause resource conflicts.
            if ($ini.ContainsKey("Group MemberShip"))
            {
                $groupMembership = @{}
                foreach ($KeyPair in $ini["Group Membership"].GetEnumerator())
                {
                    $groupName, $Property = $KeyPair.Name -split "__"
                    $GroupData = @()
                    if (![String]::IsNullOrEmpty($KeyPair.Value))
                    {
                        $GroupData = @(($KeyPair.Value -split "," | ForEach-Object { "$_" }) -join ",")
                    }

                    switch ($Property)
                    {
                        "Members"
                        {
                            if ($groupMembership.ContainsKey($groupName))
                            {
                                $groupMembership[$groupName].Members += $GroupData
                            }
                            else
                            {
                                $groupMembership[$groupName] = @{}
                                $groupMembership[$groupName].GroupName = $groupName
                                $groupMembership[$groupName].Members = $GroupData
                            }
                        }

                        "MemberOf"
                        {
                            if ($GroupData.Count -gt 0)
                            {
                                foreach ($Group in $GroupData)
                                {
                                    if ($groupMembership.ContainsKey($Group))
                                    {
                                        $groupMembership[$group].MembersToInclude += $GroupData
                                    }
                                    else
                                    {
                                        $groupMembership[$group] = @{}
                                        $groupMembership[$group].GroupName = $Group
                                        $groupMembership[$group].MembersToInclude = $GroupData
                                    }       
                                }
                            }
                        }   

                        Default
                        {
                            Write-Warning "Group Membership: $Property is not a valid Property"
                            continue
                        }
                    }
                }

                foreach ($Key in $GroupMembership.Keys)
                {
                    $CommentOut = $false
                    $configString += Write-DSCString -Resource -Name $Key -Parameters $GroupMemberShip[$key] -Type Group -CommentOut:$CommentOut
                }
            } 
        }

        # There is also SOMETIMES a RegistryXML file that contains some additional registry information.
        foreach ($XML in $PreferencesXMLs)
        {
            Write-Verbose "Reading $($XML.BaseName)XML ($($XML.FullName))"
            
            # Grab the XML info.
            [xml]$XMLContent = Get-Content $XML.FullName
            
            switch ($XML.BaseName)
            {
                "Files"
                {
                    $Settings = (Select-Xml -XPath "//$_/File" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOFilesXMLData -XML $Setting
                    }
                }

                "Folders"
                {
                    $Settings = (Select-Xml -XPath "//$_/Folder" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOFoldersXMLData -XML $Setting
                    }
                }

                "EnvironmentVariables"
                {
                    $Settings = (Select-Xml -XPath "//$_/EnvironmentVariable" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOEnvironmentVariablesXMLData -XML $Setting
                    }
                }

                "Groups"
                {
                    $Settings = (Select-Xml -XPath "//Group" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOGroupsXMLData -XML $Setting
                    }
                }
                
                "IniFiles"
                {
                    $Settings = (Select-Xml -XPath "//$_" -Xml $XMLContent).Node
                    foreach ($setting in $settings)
                    {
                        $ConfigString += Write-GPOIniFileXMLData -XML $Setting
                    }
                }
                
                "InternetSettings"
                {
                    $Settings = (Select-Xml -XPath "//Reg" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOInternetSettingsXMLData -XML $Setting
                    }
                }

                "NetworkOptions"
                {
                    <#$Settings = (Select-Xml -XPath "//$_" -xml $XMLContent).Node
 
                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPONetworkOptionsXMLData -XML $Setting
                    }#>

                    Write-Warning "ConvertFrom-GPO:$($XML.BaseName) XML file is not implemented yet."
                }

                "NetworkShareSettings"
                {
                    $Settings = (Select-Xml -XPath "//$_/NetShare" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPONetworkSharesXMLData -XML $Setting
                    }
                }

                "PowerOptions"
                {
                    $GlobalPowerOptions = (Select-Xml -XPath "//$_/GlobalPowerOptions" -xml $XMLContent).Node
                    $PowerPlans = (Select-Xml -XPath "//$_/GlobalPowerOptionsV2" -xml $XMLContent).Node                    
                    $PowerSchemes = (Select-Xml -XPath "//$_/PowerScheme" -xml $XMLContent).Node                    
                    
                    foreach ($PowerOption in $GlobalPowerOptions)
                    {
                        $ConfigString += Write-GPOGlobalPowerOptionsXMLData -XML $PowerOption
                    }

                    foreach ($PowerPlan in $PowerPlans)
                    {
                        $ConfigString += Write-GPOPowerPlanXMLData -XML $PowerPlan
                    }

                    foreach ($PowerScheme in $PowerSchemes)
                    {
                        $ConfigString += Write-GPOPowerSchemeXMLData -XML $PowerScheme
                    }

                    Write-Warning "ConvertFrom-GPO:$($XML.BaseName) XML file is not implemented yet."
                }

                "Printers"
                {
                    $Settings = (Select-Xml -XPath "//Printers" -xml $XMLContent).Node

                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOPrintersXMLData -XML $Setting
                    }
                }

                "RegionalOptions"
                {
                    $Settings = (Select-Xml -XPath "//Regional" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPORegionalOptionsXMLData -XML $Setting
                    }
                }
                
                "Registry"
                {
                    $Settings = (Select-Xml -XPath "//$_" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPORegistryXMLData -XML $Setting
                    }
                }

                "Services"
                {
                    $Settings = (Select-Xml -XPath "//NTService" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPONTServicesXMLData -XML $Setting
                    }
                }

                "Shortcuts"
                {
                    <#$Settings = (Select-Xml -XPath "//$_" -xml $XMLContent).Node
 
                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOShortcutsXMLData -XML $Setting
                    }#>

                    Write-Warning "ConvertFrom-GPO:$($XML.BaseName) XML file is not implemented yet."
                }

                "StartMenu"
                {
                    <#$Settings = (Select-Xml -XPath "//$_" -xml $XMLContent).Node
 
                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOStartMenuXMLData -XML $Setting
                    }#>

                    Write-Warning "ConvertFrom-GPO:$($XML.BaseName) XML file is not implemented yet."
                }

                "ScheduledTasks"
                {
                    $Settings = (Select-Xml -XPath "//ScheduledTasks/*" -xml $XMLContent).Node

                    # Loop through every registry setting.
                    foreach ($Setting in $Settings)
                    {
                        $ConfigString += Write-GPOScheduledTasksXMLData -XML $Setting
                    }
                }

                Default
                {
                    Write-Warning "ConvertFrom-GPO:$($XML.BaseName) XML file is not implemented yet."
                }
            }
        }
    }

    end
    {
        # Close out the Node Block and the configuration.
        $ConfigString += Write-DSCString -CloseNodeBlock 
        $ConfigString += Write-DSCString -CloseConfigurationBlock
        $ConfigString += Write-DSCString -InvokeConfiguration -Name DSCFromGPO -OutputPath $OutputPath

        if (!(Test-Path $OutputPath))
        {
            mkdir $OutputPath
        }

        # If the switch was specified, output a Configuration PS1 regardless of success or failure.
        if ($OutputConfigurationScript)
        {
            $Scriptpath = Join-Path $OutputPath "DSCFromGPO.ps1"
            Write-Verbose "Outputting Configuration SCript to $Scriptpath"
            $ConfigString | Out-File -FilePath $Scriptpath -Force -Encoding Utf8
        }

        # Create the MOF File if possible.
        $pass = Complete-Configuration -ConfigString $ConfigString -OutputPath $OutputPath
        
        if ($ShowPesterOutput)
        {
            # Write out a Summary of our parsing activities.
            Write-ProcessingHistory -Pass $Pass
        }

        if ($pass)
        {
            if ($OutputConfigurationScript)
            {
                Get-Item $Scriptpath
            }

            Get-Item $(Join-Path -Path $OutputPath -ChildPath "$ComputerName.mof") -ErrorAction SilentlyContinue
        }
        else
        {
            Get-Item $(Join-Path -Path $OutputPath -ChildPath "DSCFromGPO.ps1.error")
        }
    }
}

<#
.Synopsis
   This cmdlet converts SCM baselines into DSC Configurations.
.DESCRIPTION
   This cmdlet will look at all of the settings in an SCM XML file and convert them into DSC resources inside of a configuration.
.EXAMPLE
   dir .\SCM.XML | ConvertFROM-SCM -OutputConfigurationScript
.EXAMPLE
   ConvertFrom-SCM -Path .\SCM.XML -OutputConfigurationScript.
.EXAMPLE
   [Xml]$xml = Get-Content .\SCM.XML
   ConvertFrom-SCM -XML $xml
.INPUTS
   Either the XML content of a SCM baseline or the file itself.
.OUTPUTS
   Success or failure will yield a localhost.mof or a failed configuration file as well as an optional ConfigurationScript ps1 file.
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

Function ConvertFrom-SCM
{
    [CmdletBinding()]
    param
    (
        # This is the XML object itself.
        [Parameter(Mandatory=$true, ParameterSetName="XML", ValueFromPipeLine=$true)]
        [XML]$XML,
        
        # This is the Path to the XML file.
        [Parameter(Mandatory=$true, ParameterSetName="Path", ValueFromPipeLine=$true)]
        [ValidateScript({Test-Path $_})]
        [String]$Path,

        # Output Path that will default to an Output directory under the current Path.
        [Parameter()]
        [String]$OutputPath = $(Join-Path $pwd.Path "output"),

        # ComputerName for Node processing.
        [string]$ComputerName = "localhost",

        # This determines whether or not to output a ConfigurationScript in addition to the localhost.mof
        [switch]$OutputConfigurationScript,

        [switch]$ShowPesterOutput
    )

    # If they passed in a path we have to grab the XML object from it.
    if ($PSCmdlet.ParameterSetName -eq "Path")
    {
        [XML]$XML = Get-Content $Path
    }

    # Grab the comments from the SCM Baline XML.
    $BaselineComment = $xml.SCMPackage.Baseline.Description.Trim()

    # Start tracking Processing History.
    Clear-ProcessingHistory
    
    # Create the Configuration String
    $ConfigString = Write-DSCString -Configuration -Name DSCFromSCM -Comment $BaselineComment
    # Add any resources
    $ConfigString += Write-DSCString -ModuleImport -ModuleName PSDesiredStateConfiguration, AuditPolicyDSC, SecurityPolicyDSC
    # Add Node Data
    $ConfigString += Write-DSCString -Node -Name $ComputerName
    
    # We need to setup a namespace to properly search the XML.
    $namespace = @{e="http://schemas.microsoft.com/SolutionAccelerator/SecurityCompliance"}
    
    # Grab all the DiscoveryInfo objects in the XML. They determine how to find the setting in question.
    $results = (Select-XML -XPath "//e:SettingDiscoveryInfo" -Xml $xml -Namespace $namespace).Node

    # If we found some DiscoveryInfo objects.
    if ($results -ne $null -and $results.Count -gt 0)
    {
        foreach ($node in $results)
        {
            $Setting = "../.."
            $SettingDiscoveryInfo = ".."
            
            # Set up some variables for easy manipulation of values.
            
            # This is how to find it (.Wmidiscoveryinfo -> class, name etc.) It's only one level back in GeneratedScript
            $settingDiscoveryInfo = $node.SelectNodes($SettingDiscoveryInfo)
                
            # Grab the ID/Name from the Setting value.
            $ID = $node.SelectNodes($Setting).id.Trim("{").TrimEnd("}")
            
            # Find the ValueData using the ID.
            $valueNodeData = (Select-XML -XPath "//e:SettingRef[@setting_ref='{$($id)}']" -Xml $xml -Namespace $namespace).Node
                        
            if ($valueNodeData -eq $null)
            {
                Write-Error "Could not find ValueNodeData of $id" 
                continue
            }
            
            # Determine the DiscoveryInfo Type.
            switch ($node.DiscoveryType)
            {
                "Registry" 
                {
                    $ConfigString += Write-SCMRegistryXMLData -DiscoveryData $node -ValueData $valueNodeData
                }
                
                "Script"
                {
                    $ConfigString += Write-SCMScriptXMLData -DiscoveryData $node -ValueData $valueNodeData
                }
                
                "WMI"
                {
                    if ($GlobalConflictEngine.ContainsKey("SecurityOption"))
                    {
                        $ConfigString += Write-SCMNewSecuritySettingXMLData -DiscoveryData $node -ValueData $valueNodeData
                    }
                    else
                    {
                        $ConfigString += Write-SCMSecuritySettingXMLData -DiscoveryData $node -ValueData $valueNodeData
                    }
                }

                "AdvancedAuditPolicy"
                {
                    $ConfigString += Write-SCMAuditXMLData -DiscoveryData $node -ValueData $valueNodeData
                }
                
                "GeneratedScript (User Rights Assignment)"
                {
                    $ConfigString += Write-SCMPrivilegeXMLData -DiscoveryData $node -ValueData $valueNodeData
                }
            }
        }
    }

    # Close out our configuration string.
    $ConfigString += Write-DSCString -CloseNodeBlock
    $ConfigString += Write-DSCString -CloseConfigurationBlock
    $ConfigString += Write-DSCString -InvokeConfiguration -Name DSCFromSCM -OutputPath $OutputPath
    
    # If the switch was specified. Output a Configuration PS1 regardless of success/failure.
    if ($OutputConfigurationScript)
    {
        if (!(Test-Path $OutputPath))
        {
            mkdir $OutputPath
        }

        $Scriptpath = Join-Path $OutputPath "DSCFromSCM.ps1"
        $ConfigString | Out-File -FilePath $Scriptpath -Force -Encoding Utf8
    }

    # Try to compile the MOF file.
    $pass = Complete-Configuration -ConfigString $ConfigString -OutputPath $OutputPath
    
    # Write Summary Data on processing activities.
    Write-ProcessingHistory -Pass $Pass

    if ($pass)
    {
        if ($OutputConfigurationScript)
        {
            Get-Item $Scriptpath
        }

        Get-Item $(Join-Path -Path $OutputPath -ChildPath "$ComputerName.mof")
    }
    else
    {
        Get-Item $(Join-Path -Path $OutputPath -ChildPath "$($MyInvocation.MyCommand.Name).ps1.error")
    }
}

<#
.Synopsis
   This cmdlet converts from ASC JSON into DSC.
.DESCRIPTION
   This cmdlet will look at all baselines entries within an SCM JSON file and convert them to DSC.
.EXAMPLE
   ConvertFrom-ASC -Path .\ASC.Json
.EXAMPLE
   dir .\scm.json | ConvertFrom-ASC -OutputConfigurationScript
.INPUTS
   The ASC JSON File.
.OUTPUTS
   Success or Failure will yield detailed results along with a localhost.mof if successful or error file if unsuccessful. It also yields a ConfigurationScript on request.
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function ConvertFrom-ASC
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        # This is the Path to the JSON file.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [ValidateScript( {Test-Path $_})]
        [string]$Path,
        
        # Output Path that will default to an Output directory under the current Path.
        [ValidateScript( {Test-Path $_})]
        [string]$OutputPath = $(Join-Path $pwd.Path "Output"),

        # ComputerName for Node processing.
        [string]$ComputerName = "localhost",

        # This determines whether or not to output a ConfigurationScript in addition to the localhost.mof
        [switch]$OutputConfigurationScript, 

        [string]$BaselineName,

        [switch]$ShowPesterOutput
    )

    <# Removing temporarily for presentation
    DynamicParam
    {
        if (Test-Path $Path)
        {
            $JSON = Get-Content -Path $Path | ConvertFrom-Json
            $JSONBaselines = @()
            $JSONBaselines = $JSON.baselinerulesets.baselineName
            $JSONBaselines = $null
                                     
            $attributes = new-object System.Management.Automation.ParameterAttribute
            $attributes.ParameterSetName = "__AllParameterSets"
            $attributes.Mandatory = $false
 
            $attributeCollection = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)
             
            if ($JSONBaselines -eq $null)
            {
                return
            }
 
            $ValidateSet = new-object System.Management.Automation.ValidateSetAttribute($JSONBaselines)
 
            $attributeCollection.Add($ValidateSet)
 
            $dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("BaselineName", [string], $attributeCollection)
 
            $paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("BaselineName", $dynParam1)
 
            return $paramDictionary
        }
    }
    #>

    Begin
    {
        $BaselineName = Read-ASCBaselineName -Pattern '"baselineName": "(.*)"' -BaselineName $BaselineName -Path $Path

        if ($BaselineName -eq $null)
        {
            Throw "Select a Valid Baseline!"
        }
    }

    Process 
    {
        # JSON can be tricky to parse, so we have to put it in a Try Block in case it's not properly formatted.
        Try
        {
            $JSON = Get-Content -Path $Path | ConvertFrom-Json
        }
        Catch
        {
            Write-Error $_
            Write-Warning "Unable to parse JSON at path $Path - Exiting"
            continue
            return
        }
  
        $RULES = $JSON.baselineRulesets.Where( {$_.BaselineName -eq $BaselineName}).RULES

        # Start tracking Processing History.
        Clear-ProcessingHistory
    
        # Create the Configuration String
        $ConfigString = Write-DSCString -Configuration -Name DSCFromASC
        # Add any resources
        $ConfigString += Write-DSCString -ModuleImport -ModuleName PSDesiredStateConfiguration, AuditPolicyDSC, SecurityPolicyDSC
        # Add Node Data
        $ConfigString += Write-DSCString -Node -Name $computername
    
        # JSON is pretty straightforward where it keeps the individual settings.
        # These are the registry settings.
        $registryPolicies = $RULES.BaselineRegistryRules

        # Loop through all the registry settings.
        Foreach ($Policy in $registryPolicies)
        {
            $ConfigString += Write-ASCRegistryJSONData -RegistryData $Policy
        }

        # Grab the Audit policies.
        $AuditPolicies = $RULES.BaselineAuditPolicyRules
    
        # Loop through the Audit Policies.
        foreach ($Policy in $AuditPolicies)
        {
            $ConfigString += Write-ASCAuditJSONData -AuditData $Policy
        }

        # Grab all the Security Policy Settings.
        $securityPolicies = $RULES.BaselineSecurityPolicyRules
    
        # Loop through the Security Policies.
        foreach ($Policy in $securityPolicies)
        {
            # Security Policies can have a variety of types as they are represenations of the GPTemp.inf.
            # Determine which one the current setting is and apply.
            switch ($Policy.SectionName)
            {
                "Service General Setting"
                {

                }

                "Registry Values"
                {

                }

                "File Security"
                {

                }
                
                "Privilege Rights"
                {            
                    $ConfigString += Write-ASCPrivilegeJSONData -PrivilegeData $Policy
                }
                
                "Kerberos Policy"
                {
                
                }
                
                "Registry Keys"
                {

                }
                
                "System Access"
                {

                }
            }
        }
    
        # Close out the Configuration block.
        $ConfigString += Write-DSCString -CloseNodeBlock
        $ConfigString += Write-DSCString -CloseConfigurationBlock
        $ConfigString += Write-DSCString -InvokeConfiguration -Name DSCFromASC -OutputPath $OutputPath
    
        # If the switch was specified, output a Configuration Script regardless of success/failure.
        if ($OutputConfigurationScript)
        {
            if (!(Test-Path $OutputPath))
            {
                mkdir $OutputPath
            }
        
            $Scriptpath = Join-Path $OutputPath "DSCFromASC.ps1"
            $ConfigString | Out-File -FilePath $Scriptpath -Force -Encoding Utf8
        }

        # Try to compile configuration.
        $pass = Complete-Configuration -ConfigString $ConfigString -OutputPath $OutputPath
    
        if ($ShowPesterOutput)
        {
            # Write out Summary data of parsing history.
            Write-ProcessingHistory -Pass $Pass
        }

        if ($pass)
        {
            if ($OutputConfigurationScript)
            {
                Get-Item $Scriptpath
            }

            Get-Item $(Join-Path -Path $OutputPath -ChildPath "$ComputerName.mof")
        }
        else
        {
            Get-Item $(Join-Path -Path $OutputPath -ChildPath "DSCFromASC.ps1.error")
        }
    }
}


Function Read-ASCBaselineName
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$Pattern,

        [Parameter(Mandatory = $true)]
        [string]$Path,
        
        [Parameter()]
        [string]$BaselineName
    )

    if (!(Test-Path $Path))
    {
        Throw "$Path is not Valid!"
    }

    if (![string]::IsNullOrEmpty($BaselineName))
    {
        # Perform double validation on BaselineName just in case they typed something in and Dynamic Parameter Validation Failed.
        $Values = (Get-Item $Path | Select-String -Pattern $Pattern).Matches.Groups.Where({$_.Name -eq 1}).Value
        if ($Values -eq $null)
        {
            Throw "Could not get Baselines From ($($Path)) based on Pattern ($Pattern)"
        }
                
        if ($BaselineName -notin $Values)
        {
            Throw "$($BaselineName) is NOT a valid BaselineName"
            return $null
        }
        else
        {
            return $BaselineName
        }
    }
    else
    {
        $tmpValues = (Get-Item $Path | Select-String -Pattern $Pattern).Matches.Groups.Where({$_.Name -eq 1}).Value
                
        # Baseline was either not provided or invalid.
        # Use ISE/Shell to prompt for appropriate Baseline.
        if ($Host.Name -match "ISE")
        {
            $argPath = Join-path $PSScriptRoot "Menus\ISEMenu.ps1"
            $arguments = "-NoProfile -File " + $argPath
            $menuoutputpath = $(Join-Path $PSScriptRoot "Menus\menu_output.txt")
            $menuinputpath = $(Join-Path $PSScriptRoot "Menus\menu_input.txt")
            $tmpValues | ForEach-Object { "=$_" } | Out-File -FilePath $menuinputpath
            Remove-Item -ErrorAction SilentlyContinue $menuoutputpath
            Start-Process  -Wait -FilePath powershell.exe -ArgumentList $arguments 
            Remove-Item -ErrorAction SilentlyContinue $menuinputpath
            if (Test-Path $menuoutputpath)
            {
                $BaselineName = (Get-Content $menuoutputpath)
                return $BaselineName
            }
            else
            {
                Throw "Please Select a valid Baseline!"
            }
        }
        else
        {
            if ($tmpValues -eq $null)
            {
                Throw "Could not get Baselines From ($($Path)) based on Pattern ($Pattern)"
            }

            $Values = [ordered]@{}
            foreach ($value in $tmpValues)
            {
                $Values["$Value"] = $value
            }
            
            # Display our menu.
            $BaselineName = Show-Menu -sMenuTitle "Select a Valid Baseline" -hMenuEntries ([Ref]$Values)

            if (![string]::IsNullOrEmpty($BaselineName))
            {
                return $BaselineName
            }
            else
            {
                Throw "Please select a valid Baseline!"
            }
        }
    }
}

Function Read-CSVBaselineName
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$Path,
        
        [Parameter()]
        [string]$OS,

        [Parameter()]
        [string]$ServerType
    )

    if (!(Test-Path $Path))
    {
        Throw "$Path is not Valid!"
    }
    
    Try { $CSV = Import-CSV -Path $Path } Catch { Throw $_ } 
    
    if ($Host.Name -match "ISE")
    {
        $MenuPath = Join-Path $PSScriptRoot "Menus\menu_input.txt"
        Remove-Item -Force -Path $MenuPath -ErrorAction SilentlyContinue

        if ([string]::IsNullOrEmpty($OS) -and !([string]::IsNullOrEmpty($ServerType)))
        {
            # Prompt for OS
            foreach ($o in (($CSV.Where({$_.ServerType -match "$ServerType"})) | group).Name)
            {
                "=$o" | Add-Content $MenuPath
            }        
        }
        elseif (!([string]::IsNullOrEmpty($OS)) -and ([string]::IsNullOrEmpty($ServerType)))
        {
            # Prompt for ServerType
            foreach ($S in (($CSV.Where({$_.Filter -match "$OS"})) | group).Name)
            {
                "=$s" | Add-Content $MenuPath
            }
        }
        else
        {
            # Prompt for Both
            $ServerTypes = ($CSV.'Server Type'.ForEach({$_ -replace "(\[|\])", ""}) -split ", " | Group).Name
            foreach ($type in $ServerTypes) 
            { 
                $type | Add-Content $MenuPath
                $Entries = $CSV.Where({$_.'Server Type' -match $type})
                $Filters = $Entries.Filter.Where({![string]::IsNullOrEmpty($_)})
                $Names = ($Filters.ForEach({($_.Replace("OSVersion = [", "").Replace("]", "") -split ",\s+")}) | group).Name
                $Names.ForEach({"=$_"}) | Add-Content $MenuPath
            }
        }

        $argPath = Join-path $PSScriptRoot "Menus\ISEMenu.ps1"
        $arguments = "-NoProfile -File " + $argPath
        $menuoutputpath = $(Join-Path $PSScriptRoot "Menus\menu_output.txt")
        Remove-Item -ErrorAction SilentlyContinue $menuoutputpath
        Start-Process  -Wait -FilePath powershell.exe -ArgumentList $arguments 
        if (Test-Path $menuoutputpath)
        {
            $result = (Get-Content $menuoutputpath)
        }
        else
        {
            Throw "Please Select a valid Baseline!"
            return $null
        }
    }
    else
    {
        # Prompt for Both
        $OSHash = [ordered]@{}
        $ServerTypes = ($CSV.'Server Type'.ForEach({$_ -replace "(\[|\])", ""}) -split ", " | Group).Name
        foreach ($type in $ServerTypes) 
        { 
            $OSHash[$Type] = [ordered]@{};
            $OSHash[$Type].Title = $Type
            $Entries = $CSV.Where({$_.'Server Type' -match $type})
            $Filters = $Entries.Filter.Where({![string]::IsNullOrEmpty($_)})
            $Names = ($Filters.ForEach({($_.Replace("OSVersion = [", "").Replace("]", "") -split ",\s+")}) | group).Name
            $Names.ForEach({
                            $OSHash[$type][$_] = @{};
                            $OSHash[$type][$_].Title = $_
                            $OSHash[$type][$_].Key = "$type|$_"
                          })
        }

        # Display our menu.
        $result = Show-Menu -sMenuTitle "Select a Valid Baseline" -hMenuEntries ([Ref]$OSHash)
    }

    if (![string]::IsNullOrEmpty($result))
    {
        if ($result -match "|")
        {
            $ServerType, $OS = $result -split "\|"
        }
        else
        {
            if ([string]::IsNullOrEmpty($OS)) 
            {
                $ServerType = $result
            }
            else
            {
                $OS = $result
            }
        }

        return @{OS=$OS;ServerType=$ServerType}
    }
    else
    {
        Throw "Please select a valid Baseline!"
        return $null
    }
}

<#
.Synopsis
   This cmdlet converts from ASC JSON into DSC.
.DESCRIPTION
   This cmdlet will look at all baselines entries within an Excel CSV (Need a correct nomenclature).
.EXAMPLE
   ConvertFrom-Excel -Path .\Source.csv
.EXAMPLE
   dir .\Source.csv | ConvertFrom-Excel -OutputConfigurationScript
.INPUTS
   The Excel CSV File.
.OUTPUTS
   Success or Failure will yield detailed results along with a localhost.mof if successful or error file if unsuccessful. It also yields a ConfigurationScript on request.
.NOTES
   General notes
.COMPONENT
   The component this cmdlet belongs to
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>

function ConvertFrom-Excel
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        # This is the Path to the JSON file.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript( {Test-Path $_})]
        [string]$Path,
        
        # Output Path that will default to an Output directory under the current Path.
        [ValidateScript( {Test-Path $_})]
        [string]$OutputPath = $(Join-Path $pwd.Path "Output"),

        # ComputerName for Node processing.
        [string]$ComputerName = "localhost",

        # This determines whether or not to output a ConfigurationScript in addition to the localhost.mof
        [switch]$OutputConfigurationScript, 

        [switch]$ShowPesterOutput,

        [ValidateSet('WS2008', 'WS2008R2', 'WS2012', 'WS2012R2', 'WS2016')]
        [string]$OS,

        [ValidateSet('Domain Controller', 'Domain Member', 'Workgroup Member', 'Exclude', 'Inventory Setting')]
        [string]$ServerType
    )
    
    Begin
    {
        if (([string]::IsNullOrEmpty($OS)) -or ([string]::IsNullOrEmpty($ServerType)))
        {
            # One of the OS and ServerType was not specified, prompting will be necessary.
            $Baseline = Read-CSVBaselineName -OS $OS -ServerType $ServerType -Path $Path

            if ($Baseline -ne $null)
            {
                $OS = $Baseline.OS
                $ServerType = $Baseline.ServerType
            }
            else
            {
                Throw "Select a valid Baseline!"
            }
        }
    }

    Process 
    {
        # CSV can be tricky to parse, so we have to put it in a Try Block in case it's not properly formatted.
        Try
        {
            $CSV = Import-CSV -Path $Path 
        }
        Catch
        {
            Write-Error $_
            Write-Warning "Unable to parse CSV at path $Path - Exiting"
            continue
            return
        }
  
        $Rules = $CSV.Where({$_.Filter -match $OS -and $_.'Server Type' -match $ServerType})
        
        # Start tracking Processing History.
        Clear-ProcessingHistory
    
        # Create the Configuration String
        $ConfigString = Write-DSCString -Configuration -Name DSCFromCSV
        # Add any resources
        $ConfigString += Write-DSCString -ModuleImport -ModuleName PSDesiredStateConfiguration, AuditPolicyDSC, SecurityPolicyDSC
        # Add Node Data
        $ConfigString += Write-DSCString -Node -Name $computername
    
        # Find all of our required settings.
        $registryPolicies = $RULES.Where({$_.DataSourceType -eq "Registry"})
        $securityPolicies = $RULES.Where({$_.DataSourceType -eq "Policy"})
        $auditPolicies = $RULES.Where({$_.DataSourceType -eq "Audit"})

        # Loop through all the registry settings.
        Foreach ($Policy in $registryPolicies)
        {
            $ConfigString += Write-RegistryCSVData -RegistryData $Policy
        }

        # Loop through the Audit Policies.
        foreach ($Policy in $AuditPolicies)
        {
            $ConfigString += Write-AuditCSVData -AuditData $Policy
        }

        # Loop through the Security Policies.
        foreach ($Policy in $securityPolicies)
        {
            # Security Policies can have a variety of types as they are represenations of the GPTemp.inf.
            # Determine which one the current setting is and apply.
            switch -regex ($Policy.DataSourceKey)
            {
                "Service General Setting"
                {

                }

                "Registry Values"
                {

                }

                "File Security"
                {

                }
                
                "Privilege Rights"
                {            
                    $ConfigString += Write-PrivilegeCSVData -PrivilegeData $Policy
                }
                
                "Kerberos Policy"
                {
                
                }
                
                "Registry Keys"
                {

                }
                
                "System Access"
                {

                }
            }
        }
    
        # Close out the Configuration block.
        $ConfigString += Write-DSCString -CloseNodeBlock
        $ConfigString += Write-DSCString -CloseConfigurationBlock
        $ConfigString += Write-DSCString -InvokeConfiguration -Name DSCFromCSV -OutputPath $OutputPath
    
        # If the switch was specified, output a Configuration Script regardless of success/failure.
        if ($OutputConfigurationScript)
        {
            if (!(Test-Path $OutputPath))
            {
                mkdir $OutputPath
            }
        
            $Scriptpath = Join-Path $OutputPath "DSCFromCSV.ps1"
            $ConfigString | Out-File -FilePath $Scriptpath -Force -Encoding Utf8
        }

        # Try to compile configuration.
        $pass = Complete-Configuration -ConfigString $ConfigString -OutputPath $OutputPath
    
        if ($ShowPesterOutput)
        {
            # Write out Summary data of parsing history.
            Write-ProcessingHistory -Pass $Pass
        }

        if ($pass)
        {
            if ($OutputConfigurationScript)
            {
                Get-Item $Scriptpath
            }

            Get-Item $(Join-Path -Path $OutputPath -ChildPath "$ComputerName.mof")
        }
        else
        {
            Get-Item $(Join-Path -Path $OutputPath -ChildPath "DSCFromCSV.ps1.error")
        }
    }
}

function Merge-GPOs
{
<#
.Synopsis
 Merge all applied GPOs for a computer into a MOF-file using the ConvertFrom-GPO function.
.DESCRIPTION
 This function will allow you to create a MOF-file of one computers all applied Group Policy Objects.
 The computer can be the local machine or a remote machine.
 For remote connections Windows Remote Management needs to be configured and port 5985 open in the firewall.
 The PowerShell function ConvertFrom-GPO is required.
.PARAMETER Computer
 The name of the computer to use as an template for the MOF-file
 .PARAMETER Path
 The folder where the MOF-file will be created.
 .PARAMETER Table
 Display table of merged GPOs.
 .PARAMETER SkipGPUpdate
 Do not execute gpupdate on computer.
.EXAMPLE
 Merge-GPOs
 Locally collect applied group policy links and merge the into one MOF-file.
.EXAMPLE
 Merge-GPOs -Computer Server1
 Remotely collect applied group policy links and merge the into one MOF-file.
.EXAMPLE
 Merge-GPOs -Computer Server1 -Path C:\Temp
 Remotely collect applied group policy links and merge the into one MOF-file in the folder C:\Temp
.EXAMPLE
 Merge-GPOs -Computer Server1 -SkipTable
 Remotely collect applied group policy links and merge the into one MOF-file but do not present the applied GPOs on screen.
 .EXAMPLE
 Merge-GPOs -Computer Server1 -SkipGPUpdate
 Will not perform a gpupdate before collecting applied group policy links and merge the into one MOF-file.
 If recent group policy changes have been made these could then be missing in the MOF-file.
.INPUTS
.OUTPUTS
.COMPONENT
.ROLE
#>



    [CmdletBinding(DefaultParameterSetName='Filters', 
                  SupportsShouldProcess=$true, 
                  PositionalBinding=$false,
                  HelpUri = 'http://www.microsoft.com/',
                  ConfirmImpact='Medium')]
    [Alias()]
    [OutputType([String])]
    Param
    (
        # Computer to scan
        [Parameter(Mandatory=$false, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0,
                   ParameterSetName='Computer')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [String] 
        $Computer=$env:COMPUTERNAME,
        # Output Path that will default to an Output directory under the current Path.
        [ValidateScript( {Test-Path $_ -PathType Container})]
        [string]$Path = $(Join-Path $pwd.Path "Output"),
        # Display all merged GPOs
        [Parameter(Mandatory=$false, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=2,
                   ParameterSetName='Computer')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [switch] 
        $SkipTable,
        # Do not perform GPUpdate on host
        [Parameter(Mandatory=$false, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=3,
                   ParameterSetName='Computer')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [switch] 
        $SkipGPUpdate,
        [Parameter(Mandatory=$false, 
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false, 
        ValueFromRemainingArguments=$false, 
        Position=3,
        ParameterSetName='Computer')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [switch] 
        $ShowPesterOutput,
        [Parameter(Mandatory=$false, 
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false, 
        ValueFromRemainingArguments=$false, 
        Position=3,
        ParameterSetName='Computer')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [switch] 
        $OutputConfigurationScript
    )
Begin
{


$AppliedGPLink = New-Object System.Collections.ArrayList
#==========================================================================
# Function : PingHost
# Arguments : host, timeout
# Returns : boolean
# Description : Ping a host and returns results in form of boolean
#
#==========================================================================
Function PingHost {
    param([Array]$hostlist,[Array]$ports,[Int]$timeout = "1")
    $ErrorActionPreference = "SilentlyContinue"
    $ping = new-object System.Net.NetworkInformation.Ping
    foreach ($ip in $hostlist)
    {
        $rslt = $ping.send($ip,$timeout)
        if (! $?)
        {
            return $false
        }
        else
        {
            return $true
        }
    }

}

#==========================================================================
# Function : PortPing
# Arguments : host, port timeout
# Returns : boolean
# Description : Ping a port number and returns results in form of boolean
#
#==========================================================================
Function PortPing
{
Param([string]$srv,$port=135,$timeout=3000,[switch]$verbose)

# Test-Port.ps1
# Does a TCP connection on specified port (135 by default)

$ErrorActionPreference = "SilentlyContinue"

# Create TCP Client
$tcpclient = new-Object system.Net.Sockets.TcpClient

# Tell TCP Client to connect to machine on Port
$iar = $tcpclient.BeginConnect($srv,$port,$null,$null)

# Set the wait time
$wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)

# Check to see if the connection is done
if(!$wait)
{
    # Close the connection and report timeout
    $tcpclient.Close()
    if($verbose){Write-Host "Connection Timeout"}
    Return $false
}
else
{
    # Close the connection and report the error if there is one
    $error.Clear()
    $tcpclient.EndConnect($iar) | out-Null
    if(!$?){if($verbose){write-host $error[0]};$failed = $true}
    $tcpclient.Close()
}

# Return $true if connection Establish else $False
if($failed){return $false}else{return $true}
}
#==========================================================================
# Function : Wait-MyJob
# Arguments : job Ids
# Returns : Array of objects returned by command
# Description : Wait for jobs and return output
#
#==========================================================================
Function Wait-MyJob
{
param
(
    # This is the Path of the GPO Backup Directory.
    [Parameter(Mandatory = $true)]
    $jobIds,
    [Parameter(Mandatory = $false)]
    [switch]
    $NoReturn
)
[int]$Timeout = 100
$sleepTime = 2
$timeElapsed =  0
$running = $true
$dicCompltedJobs = @{}
$arrReturnedData = New-Object System.Collections.ArrayList
while ($running -and $timeElapsed -le $Timeout)
{
    $running = $false

    $jobs = get-job | where{$jobIds.Contains($_.Id)}
    #Reporting job state
    $colrunningjobs = get-job | where{$jobIds.Contains($_.Id)} | where State -eq 'Running' | Select-Object -Property Name
    Foreach($runningjob in $colrunningjobs)
    {
        Write-Verbose "Waiting for job to complete on: $($runningjob.name)" 
    }
    $colcompletedjobs = get-job | where{$jobIds.Contains($_.Id)} | where State -eq 'Completed' | Select-Object -Property Name
    Foreach($completedjob in $colcompletedjobs)
    {
        If (!($dicCompltedJobs.ContainsKey($completedjob.name)))
        {
            $dicCompltedJobs.Add($completedjob.name,'Completed')

            Write-Verbose "Job completed on $($runningjob.name)"
        }
    }
            

    Foreach($job in $jobs)
    {
        if($job.State -eq 'Running')
        {
            $running = $true
        }
    }

    Start-Sleep $sleepTime
    $timeElapsed += $sleepTime
}

$jobs = get-job | where{$jobIds.Contains($_.Id)}
Foreach($job in $jobs)
{
    if($job.State -eq 'Failed')
    {
        Write-Warning "Job failed on $($runningjob.name)"
    }
    else
    {
            Receive-Job $job | %{[void]$arrReturnedData.add($_)}

    }
}
#If NoRetrun is specified do not return data
if(!($NoReturn))
{
    return $arrReturnedData
}
}
}
Process
{
$bolConnectionEstablished = $false
#Test if computer responds on ping
if($(PingHost $Computer))
{
    #Test if computer accept incoming traffic on port 5985 (WinRM)
    if($(PortPing $Computer 5985 3000))
    {
        #Open session to computer
        $PSSession = New-PSSession -ComputerName $Computer

        #if GPUpdate is requested, run it remotely to get latest GP links
        if(!($SkipGPUpdate))
        {
            #Commands to refresh applied gpos
            $Script = {
                gpupdate /target:computer
            }
            #Reset jobs array
            $MyjobIds = @()
            Write-Verbose "Perform GPUPDATE [/target:computer] on $Computer"

            #Execute command remotely
            $job = Invoke-Command -Session $PSSession -ScriptBlock $Script -AsJob -JobName "$Computer" 

            #Add job to array
            $MyjobIds += $job.Id

            if($VerbosePreference -eq "Continue")
            {
                #Wait for jobb and return output
                Wait-MyJob $MyjobIds 
            }
            else
            {
                #Wait for jobb and do not return output
                Wait-MyJob $MyjobIds -NoReturn
            }
        }

        #Reset Jobs to wait for
        $MyjobIds = @()

        #Commands to retreive applied GPOs
        $Script = {
            $AppliedGPLink =Get-WmiObject -Namespace "ROOT\RSOP\Computer" -Class 'RSOP_GPlink' -Filter 'AppliedOrder <> 0' | Select-Object -Property GPO,appliedOrder,SOM | Sort-Object -Property appliedOrder -Descending
            return $AppliedGPLink
        }
        Write-Verbose "Collect applied GPOs from $Computer"
        #Execute command remotely
        $job = Invoke-Command -Session $PSSession -ScriptBlock $Script -AsJob -JobName "$Computer" 
        #Add job to array
        $MyjobIds += $job.Id
        #Wait for job and retrive results
        $AppliedGPLink = @(Wait-MyJob $MyjobIds)
        $bolConnectionEstablished = $true
    }
    else
    {
        Write-Error -Category ConnectionError  -Message "$Computer has port 5985 closed!"
        #exit function
        Break
    }
}
else
{
    Write-Error -Category ConnectionError  -Message "$Computer not available!"
    #exit function
    Break
}
if($AppliedGPLink.count -gt 0)
{
    if($bolConnectionEstablished)
    {
        $strBackupFolder = $Path + "\MergedGPOs"
        $tmpGUId = (New-Guid).GUID.tostring()
        $script:bolDeleteSuccess = $true
        if(Test-Path -Path $strBackupFolder){try{Remove-Item -Path $strBackupFolder -Recurse -Force -Confirm:$false -ErrorAction stop ;$script:bolDeleteSuccess = $true }catch{$script:bolDeleteSuccess = $false}}
        if($script:bolDeleteSuccess)
        {
            $objBackupFolder = New-Item -ItemType Directory -Path $strBackupFolder
        }
        else
        {

            $strBackupFolder = $strBackupFolder + $tmpGUId
            Write-Verbose  "Failed to delete old folder, creating new folder with trailing GUID. $strBackupFolder"  
            $objBackupFolder = New-Item -ItemType Directory -Path $strBackupFolder
        }
        #Counter for GPlinks will be used as prefix on the folde name
        $i = 1
        #Enumerate GPO links
        $arrPresentGPOsApplied = new-object System.Collections.ArrayList
        Foreach ($GPLink in $AppliedGPLink)
        {

            #Remove first part
            $GUID = $GPLink.GPO.toString().split("{")[1]

            #Remove last part
            $GUID = $GUID.toString().split("}")[0]
            $GPOName =  $((Get-GPO -Guid $GUID).DisplayName.toString() ) 

            #Remove non-allowed folder characters
            $strFolderName = $GPOName -replace '[\x2B\x2F\x22\x3A\x3C\x3E\x3F\x5C\x7C]', ''

            #Naming the folder with reversed numbering from the applied order to get merged the mof correctly
            $strFolderFullName = $strBackupFolder+"\"+$i+"_"+$strFolderName
            Write-Verbose "Exporting GPO: $GPOName"

            #Delete GPO backup destination folder if already exist
            if(Test-Path -Path $strFolderFullName){Remove-Item -Path $strFolderFullName -Recurse -Force -Confirm:$false}

            #Create GPO backup destination folder
            $BackupDestination = New-Item -ItemType Directory -Path $strFolderFullName

            #Backup GPO in folder created
            $nul=Backup-GPO -Guid $GUID -Path $BackupDestination.FullName

            #Trim GPLink SOM value
            $strSOM = $GPLink.SOM.tostring()
            #Get the value between the double qoutes.
            $strSOM = $strSOM.split('"')[1]
            #New object for presenting each GPO
            $objGPO = New-Object PSObject
            Add-Member -inputObject $objGPO -memberType NoteProperty -name "Name" -value $GPOName 
            Add-Member -inputObject $objGPO -memberType NoteProperty -name "Applied Order" -value $GPLink.appliedOrder
            Add-Member -inputObject $objGPO -memberType NoteProperty -name "Merged Order" -value $i
            Add-Member -inputObject $objGPO -memberType NoteProperty -name "SOM" -value $strSOM
            #Add GPO object to array
            [VOID]$arrPresentGPOsApplied.add($objGPO)

            #Incrementing counter of GPlinks for folder names
            $i++

        }
        #Clean up
        $AppliedGPLink = ""
        $BackupDestination = ""
        $strFolderFullName = ""
        $GPOName = ""  
        $GUID = ""
        $GPLink = ""
        $tmpGUId = ""
        Remove-Variable AppliedGPLink
        Remove-Variable BackupDestination
        Remove-Variable strFolderFullName
        Remove-Variable GPOName
        Remove-Variable GUID
        Remove-Variable GPLink
        Remove-Variable tmpGUId

        Write-Verbose ("Converting GPOs to MOF-file")

        $rslt = Get-ChildItem -Path $strBackupFolder -Directory -Filter "{*" -Recurse | ConvertFrom-GPO -ComputerName ($Computer+"_Merged") -OutputPath $Path -OutputConfigurationScript:$OutputConfigurationScript -ShowPesterOutput:$ShowPesterOutput

    }#end if connectionestablised
}
else
{
    Write-Output "No applied GPLink found!"
}
    
}
End
{
    #Check if objBackupFolder exist
    if($objBackupFolder)
    {
        #Check if objBackupFolder path exist
        if(Test-Path $objBackupFolder)
        {
            #Delete exported files
            remove-item $objBackupFolder -Recurse -Force -Confirm:$false
        }
    }
    #User selects if the table of merged GPOs should not be presented
    if(!($SkipTable))
    {
        $arrPresentGPOsApplied | Format-Table -Property Name,'Applied Order','Merged Order',SOM -AutoSize
    }
    #Verify that ConvertFrom-GPO has yielded an output
    if($rslt)
    {
        Write-Output "Output file: $($rslt.Fullname)"
    }
    #Clean up
    $objBackupFolder = ""
    $rslt = ""
    $arrPresentGPOsApplied = ""
    Remove-Variable rslt
    Remove-Variable arrPresentGPOsApplied
    Remove-Variable objBackupFolder

}
}

Export-ModuleMember -Function ConvertFrom-SCM, ConvertFrom-ASC, ConvertFrom-GPO, ConvertTo-DSC, ConvertFrom-Excel, Merge-GPOs