DscResources/DscLcmController/DscLcmController.schema.psm1

$dscLcmControllerScript = @'
function Send-DscTaggingData {
    [CmdletBinding()]
    param()

    $pattern = 'http[s]?:\/\/(?<PullServer>([^\/:\.[:space:]]+(\.[^\/:\.[:space:]]+)*)|([0-9](\.[0-9]{3})))(:[0-9]+)?((\/[^?#[:space:]]+)(\?[^#[:space:]]+)?(\#.+)?)?'
    try {
        $lcm = Get-DscLocalConfigurationManager -ErrorAction Stop
        $pullServerUrl = $lcm.ConfigurationDownloadManagers.ServerURL
        $agentId = $lcm.AgentId

        Write-Host "PullServerUrl = '$pullServerUrl'"
        Write-Host "AgentId = '$agentId'"

        $found = $pullServerUrl -match $pattern

        if (-not $found) {
            Write-Error "Could not find pull server in Url '$pullServerUrl'" -ErrorAction Stop
        }
    }
    catch {
        Write-Error "Cannot get pull server name from 'Get-DscLocalConfigurationManager' output, the error was $($_.Exception.Message)"
        return
    }

    $versionData = Invoke-Command -ComputerName $env:COMPUTERNAME -ConfigurationName DSC -ScriptBlock {
        Get-DscConfigurationVersion
    }
    if ($versionData.Layers.Count -gt 1) {
        $versionData.Layers = $versionData.Layers -join ', '
    }

    Write-Host
    Write-Host "Sending the following DSC version data to JEA endpoint on pull server '$($Matches.PullServer)'"
    $versionData | Out-String | Write-Host
    
    Invoke-Command -ComputerName $Matches.PullServer -ConfigurationName DscData -ScriptBlock {
        Send-DscTaggingData -AgentId $args[0] -Data $args[1]
    } -ArgumentList $agentId, $versionData
}

function Set-LcmPostpone {
    $postponeInterval = 14
    if ($lastLcmPostpone.AddDays($postponeInterval) -gt (Get-Date)) {
        Write-Host "Last LCM postpone was done at '$lastLcmPostpone'. Next one will not be triggered before '$($lastLcmPostpone.AddDays($postponeInterval))'"
        Write-Host
        return
    }
    else {
        Write-Host "Last LCM postpone was done at '$lastLcmPostpone'. Triggering LCM postone as the last time was more than $postponeInterval ago"
        Write-Host
    }

    $currentLcmSettings = Get-DscLocalConfigurationManager
    $maxConsistencyCheckInterval = if ($currentLcmSettings.ConfigurationModeFrequencyMins -eq 44640) {
        44639 #value must be changed in order to reset the LCM timer
    }
    else {
        44640 #minutes for 31 days
    }
    
    $maxRefreshInterval = if ($currentLcmSettings.RefreshFrequencyMins -eq 44640) {
        44639 #value must be changed in order to reset the LCM timer
    }
    else {
        44640 #minutes for 31 days
    }
    
    $metaMofFolder = mkdir -Path "$path\MetaMof" -Force
    
    if (Test-Path -Path C:\Windows\System32\Configuration\MetaConfig.mof) {
        $mofFile = Copy-Item -Path C:\Windows\System32\Configuration\MetaConfig.mof -Destination "$path\MetaMof\localhost.meta.mof" -Force -PassThru
    }
    else {
        $mofFile = Get-Item -Path "$path\MetaMof\localhost.meta.mof" -ErrorAction Stop
    }
    $content = Get-Content -Path $mofFile.FullName -Raw -Encoding Unicode
    
    $pattern = '(ConfigurationModeFrequencyMins(\s+)?=(\s+)?)(\d+)(;)'
    $content = $content -replace $pattern, ('$1 {0}$5' -f $maxConsistencyCheckInterval)
    
    $pattern = '(RefreshFrequencyMins(\s+)?=(\s+)?)(\d+)(;)'
    $content = $content -replace $pattern, ('$1 {0}$5' -f $maxRefreshInterval)
    
    $content | Out-File -FilePath $mofFile.FullName -Encoding unicode
    
    Set-DscLocalConfigurationManager -Path $metaMofFolder
    
    "$(Get-Date) - Postponed LCM" | Add-Content -Path "$path\LcmPostponeSummary.log"
    
    Set-ItemProperty -Path $dscLcmController.PSPath -Name LastLcmPostpone -Value (Get-Date) -Type String -Force
}

function Test-InMaintenanceWindow {
    if ($maintenanceWindows) {
        $inMaintenanceWindow = foreach ($maintenanceWindow in $maintenanceWindows) {
            Write-Host "Reading maintenance window '$($maintenanceWindow.PSChildName)'"
            [datetime]$startTime = Get-ItemPropertyValue -Path $maintenanceWindow.PSPath -Name StartTime
            [timespan]$timespan = Get-ItemPropertyValue -Path $maintenanceWindow.PSPath -Name Timespan
            [datetime]$endTime = $startTime + $timespan
            [string]$dayOfWeek = try {
                Get-ItemPropertyValue -Path $maintenanceWindow.PSPath -Name DayOfWeek
            }
            catch { }
            [string]$on = try {
                Get-ItemPropertyValue -Path $maintenanceWindow.PSPath -Name On
            }
            catch { }

            if ($dayOfWeek) {
                if ((Get-Date).DayOfWeek -ne $dayOfWeek) {
                    Write-Host "DayOfWeek is set to '$dayOfWeek'. Current day of week is '$((Get-Date).DayOfWeek)', maintenance window does not apply"
                    continue
                }
                else {
                    Write-Host "Maintenance Window is configured for week day '$dayOfWeek' which is the current day of week."
                }
            }

            if ($on) {

                if ($on -ne 'last') {
                    $on = [int][string]$on[0]
                }

                $daysInMonth = [datetime]::DaysInMonth($now.Year, $now.Month)
                $daysInMonth = for ($i = 1; $i -le $daysInMonth; $i++) {
                    Get-Date -Date $now -Day $i
                }

                $daysInMonth = $daysInMonth | Where-Object { $_.DayOfWeek -eq $dayOfWeek }

                $daysInMonth = if ($on -eq 'last') {
                    $daysInMonth | Select-Object -Last 1
                }
                else {
                    $daysInMonth | Select-Object -Index ($on - 1)
                }

                if ($daysInMonth.ToShortDateString() -ne $now.ToShortDateString()) {
                    Write-Host "Today is not the '$on' $dayOfWeek in the current month"
                    continue
                }
                else {
                    Write-Host "The LCM is supposed to run on the '$on' $dayOfWeek which applies to today"
                }
            }

            Write-Host "Maintenance window: $($startTime) - $($endTime)."
            if ($currentTime -gt $startTime -and $currentTime -lt $endTime) {
                Write-Host "Current time '$currentTime' is in maintenance window '$($maintenanceWindow.PSChildName)'"

                Write-Host "IN MAINTENANCE WINDOW: Setting 'inMaintenanceWindow' to 'true' as the current time is in a maintanence windows."
                $true
                break
            }
            else {
                Write-Host "Current time '$currentTime' is not in maintenance window '$($maintenanceWindow.PSChildName)'"
            }
        }
    }
    else {
        Write-Host "No maintenance windows defined. Setting 'inMaintenanceWindow' to 'false'."
        $false
    }
    Write-Host

    if (-not $inMaintenanceWindow -and $maintenanceWindowOverride) {
        Write-Host "OVERRIDE: 'inMaintenanceWindow' is 'false' but 'maintenanceWindowOverride' is enabled, setting 'inMaintenanceWindow' to 'true'"
        $true
    }
    elseif (-not $inMaintenanceWindow) {
        Write-Host "NOT IN MAINTENANCE WINDOW: 'inMaintenanceWindow' is 'false'. The current time is not in any of the $($maintenanceWindows.Count) maintenance windows."
        $false
    }
    else {
        $inMaintenanceWindow
    }
}

function Set-LcmMode {
    param(
        [Parameter(Mandatory)]
        [ValidateSet('ApplyAndAutoCorrect', 'ApplyAndMonitor')]
        [string]$Mode
    )
    $metaMofFolder = mkdir -Path "$path\MetaMof" -Force
    
    if (Test-Path -Path C:\Windows\System32\Configuration\MetaConfig.mof) {
        $mofFile = Copy-Item -Path C:\Windows\System32\Configuration\MetaConfig.mof -Destination "$path\MetaMof\localhost.meta.mof" -Force -PassThru
    }
    else {
        $mofFile = Get-Item -Path "$path\MetaMof\localhost.meta.mof" -ErrorAction Stop
    }
    $content = Get-Content -Path $mofFile.FullName -Raw -Encoding Unicode
    
    $pattern = '(ConfigurationMode(\s+)?=(\s+)?)("\w+")(;)'
    $content = $content -replace $pattern, ('$1 "{0}"$5' -f $Mode)
    
    $content | Out-File -FilePath $mofFile.FullName -Encoding unicode
    
    Set-DscLocalConfigurationManager -Path $metaMofFolder

    Write-Host "LCM put into '$Mode' mode"
}

function Test-StartDscAutoCorrect {
    if ($maintenanceWindowMode -eq 'AutoCorrect') {

        $nextAutoCorrect = $lastAutoCorrect + $autoCorrectInterval
        Write-Host ""
        Write-Host "The previous AutoCorrect was done on '$lastAutoCorrect', the next one will not be triggered before '$nextAutoCorrect'. AutoCorrectInterval is $autoCorrectInterval."
        if ($currentTime -gt $nextAutoCorrect) {
            Write-Host 'It is time to trigger an AutoCorrect per the defined interval.'
            $doAutoCorrect = $true
        }
        else {
            if ($autoCorrectIntervalOverride) {
                Write-Host "OVERRIDE: It is NOT time to trigger an AutoCorrect per the defined interval but 'AutoCorrectIntervalOverride' is enabled."
                $doAutoCorrect = $true
            }
            else {
                Write-Host 'It is NOT time to trigger an AutoCorrect per the defined interval.'
                $doAutoCorrect = $false
            }
        }
        $doAutoCorrect
    }
    else {
        $false
    }
    
}

function Test-StartDscRefresh {
    if ($maintenanceWindowMode -eq 'AutoCorrect') {

        $nextRefresh = $lastRefresh + $refreshInterval
        Write-Host ""
        Write-Host "The previous Refresh was done on '$lastRefresh', the next one will not be triggered before '$nextRefresh'. RefreshInterval is $refreshInterval."
        if ($currentTime -gt $nextRefresh) {
            Write-Host 'It is time to trigger a Refresh per the defined interval.'
            $doRefresh = $true
        }
        else {
            if ($refreshIntervalOverride) {
                Write-Host "OVERRIDE: It is NOT time to trigger a Refresh check per the defined interval but 'refreshIntervalOverride' is enabled."
                $doRefresh = $true
            }
            else {
                Write-Host 'It is NOT time to trigger a Refresh check per the defined interval.'
                $doRefresh = $false
            }
        }
        $doRefresh
    }
    else {
        $false
    }
    
}

function Start-AutoCorrect {
    Write-Host "ACTION: Invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags '1' (Consistency Check)."
    try {
        $script:lcmRuntime = Start-LcmRequiredConfigurationChecks -Mode AutoCorrect -MaxLcmRuntime $maxLcmRuntime -Flags 1
        $dscLcmController = Get-Item -Path HKLM:\SOFTWARE\DscLcmController
        Set-ItemProperty -Path $dscLcmController.PSPath -Name LastAutoCorrect -Value (Get-Date) -Type String -Force
    }
    catch {
        Write-Error "Error invoking 'PerformRequiredConfigurationChecks'. The message is: '$($_.Exception.Message)'"
        $script:autoCorrectErrors = $true
    }
}

function Start-Monitor {
    Write-Host "ACTION: Invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags '1' (Consistency Check)."
    try {
        $script:lcmRuntime = Start-LcmRequiredConfigurationChecks -Mode Monitor -MaxLcmRuntime $maxLcmRuntime -Flags 1
        $dscLcmController = Get-Item -Path HKLM:\SOFTWARE\DscLcmController
        Set-ItemProperty -Path $dscLcmController.PSPath -Name LastMonitor -Value (Get-Date) -Type String -Force
    }
    catch {
        Write-Error "Error invoking 'PerformRequiredConfigurationChecks'. The message is: '$($_.Exception.Message)'"
        $script:monitorErrors = $true
    }
}

function Start-Refresh {
    Write-Host "ACTION: Invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags'5' (Pull and Consistency Check)."
    try {
        $script:lcmRuntime = Start-LcmRequiredConfigurationChecks -Mode AutoCorrect -MaxLcmRuntime $maxLcmRuntime -Flags 5
        $dscLcmController = Get-Item -Path HKLM:\SOFTWARE\DscLcmController
        Set-ItemProperty -Path $dscLcmController.PSPath -Name LastRefresh -Value (Get-Date) -Type String -Force
        if ($sendDscTaggingData) {
            try {
                Send-DscTaggingData -ErrorAction Stop
            }
            catch {
                $sendDscTaggingDataError = $true
            }
        }
    }
    catch {
        Write-Error "Error invoking 'PerformRequiredConfigurationChecks'. The message is: '$($_.Exception.Message)'"
        $script:refreshErrors = $true
    }
}

function Test-StartDscMonitor {
    $nextMonitor1 = $lastMonitor + $monitorInterval
    $nextMonitor2 = $lastAutoCorrect + $monitorInterval
    $nextMonitor = [datetime][math]::Max($nextMonitor1.Ticks, $nextMonitor2.Ticks)

    Write-Host ''
    Write-Host "The previous Monitor was done on '$lastMonitor', the next one will not be triggered before '$nextMonitor'. MonitorInterval is $monitorInterval."
    if ($currentTime -gt $nextMonitor) {
        Write-Host 'It is time to trigger a Monitor per the defined interval.'
        $doMonitor = $true
    }
    else {
        Write-Host 'It is NOT time to trigger a Monitor per the defined interval.'
        $doMonitor = $false
    }
    $doMonitor
}

function Start-LcmRequiredConfigurationChecks {
    param(
        [OutputType([timespan])]
        [Parameter()]
        [timespan]$MaxLcmRuntime = (New-TimeSpan -Days 2),
        
        [Parameter(Mandatory)]
        [ValidateSet('Monitor', 'AutoCorrect')]
        [string]$Mode,
        
        [Parameter(Mandatory)]
        [int]$Flags
    )
    Write-Verbose "Entering 'Start-LcmRequiredConfigurationChecks'"
    
    $internalMaxLcmRuntime = $MaxLcmRuntime
    
    $j = Start-Job -ScriptBlock {
        param(
            [Parameter(Mandatory)]
            [int]$Flags
        )
        $params = @{
            ClassName = 'MSFT_DSCLocalConfigurationManager'
            Namespace = 'root/Microsoft/Windows/DesiredStateConfiguration'
            MethodName = 'PerformRequiredConfigurationChecks'
            Arguments = @{ Flags = [uint32]$Flags }
            ErrorAction = 'Stop'
        }
        Write-Output "Calling 'Invoke-CimMethod' with the following parameters:"
        $params | ConvertTo-Json | Write-Output
        Invoke-CimMethod @params | Out-Null
        
    } -ArgumentList $Flags
    
    Write-Host "Waiting $MaxLcmRuntime for the background job to finish."
    
    while ($j.State -eq 'Running' -and $internalMaxLcmRuntime -gt 0) {
        $waitIntervalInSeconds = 5
        Start-Sleep -Seconds $waitIntervalInSeconds
        $output = $j | Receive-Job | Out-String
        if ($output) { $output | Write-Host }
        $internalMaxLcmRuntime = $internalMaxLcmRuntime.Subtract((New-TimeSpan -Seconds $waitIntervalInSeconds))
    }
    
    if ($j.State -eq 'Running') {
        Write-Host "LCM did not finish with the timeout of '$MaxLcmRuntime'"
        $j | Stop-Job
        #find the process that is hosting the DSC engine
        $dscProcess = Get-CimInstance -ClassName msft_providers | Where-Object { $_.Provider -like 'dsccore' }
        Write-Host "Shutting down LCM process with ID '$($dscProcess.HostProcessIdentifier)'"
        Get-Process -Id $dscProcess.HostProcessIdentifier | Stop-Process -Force
        if ($Mode -eq 'AutoCorrect') {
            Set-ItemProperty -Path $dscLcmController.PSPath -Name LastAutoCorrect -Value (Get-Date -Date 0) -Type String -Force
        }
        else {
            Set-ItemProperty -Path $dscLcmController.PSPath -Name LastMonitor -Value (Get-Date -Date 0) -Type String -Force
        }
        Write-Error -Message "LCM did run longer than '$MaxLcmRuntime'. Process was stopped." -ErrorAction Stop
    }
    
    $runtime = $j.PSEndTime - $j.PSBeginTime
    Write-Host "LCM runtime was '$runtime'"
    $runtime
}

$writeTranscripts = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name WriteTranscripts
$path = Join-Path -Path ([System.Environment]::GetFolderPath('CommonApplicationData')) -ChildPath 'Dsc\LcmController'

if ($writeTranscripts) {
    Start-Transcript -Path "$path\LcmController.log" -Append
}

#Disable DSC Timer
$timer = Get-CimInstance -ClassName msft_providers | Where-Object { $_.Provider -like 'dsctimer' }
$timer | Invoke-CimMethod -MethodName UnLoad

$now = Get-Date
$currentConfigurationMode = (Get-DscLocalConfigurationManager).ConfigurationMode
$lcmModeChanged = ''
$doConsistencyCheck = $false
$doRefresh = $false
$inMaintenanceWindow = $false
$doAutoCorrect = $false
$doRefresh = $false
$doMonitor = $false
$autoCorrectErrors = $false
$refreshErrors = $false
$monitorErrors = $false
$currentTime = Get-Date
$lcmRuntime = $null
$sendDscTaggingData = $false
$sendDscTaggingDataError = $false
$dscLcmController = Get-Item -Path HKLM:\SOFTWARE\DscLcmController

$maintenanceWindows = Get-ChildItem -Path HKLM:\SOFTWARE\DscLcmController\MaintenanceWindows
[bool]$maintenanceWindowOverride = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name MaintenanceWindowOverride
[timespan]$autoCorrectInterval = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name AutoCorrectInterval
[bool]$autoCorrectIntervalOverride = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name AutoCorrectIntervalOverride
[timespan]$monitorInterval = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name MonitorInterval
[timespan]$refreshInterval = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name RefreshInterval
[bool]$refreshIntervalOverride = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name RefreshIntervalOverride
[timespan]$maxLcmRuntime = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name MaxLcmRuntime
[timespan]$logHistoryTimeSpan = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name LogHistoryTimeSpan
[bool]$sendDscTaggingData = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name SendDscTaggingData
$maintenanceWindowMode = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name MaintenanceWindowMode

[datetime]$lastAutoCorrect = try {
    Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name LastAutoCorrect
}
catch {
    Get-Date -Date 0
}
[datetime]$lastMonitor = try {
    Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name LastMonitor
}
catch {
    Get-Date -Date 0
}
[datetime]$lastRefresh = try {
    Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name LastRefresh
}
catch {
    Get-Date -Date 0
}
[datetime]$lastLcmPostpone = try {
    Get-ItemPropertyValue -Path HKLM:\SOFTWARE\DscLcmController -Name LastLcmPostpone
}
catch {
    Get-Date -Date 0
}

Write-Host '----------------------------------------------------------------------------'
Set-LcmPostpone

$inMaintenanceWindow = Test-InMaintenanceWindow
Write-Host
if ($inMaintenanceWindow) {
    if ($maintenanceWindowMode -eq 'AutoCorrect' -and $currentConfigurationMode -ne 'ApplyAndAutoCorrect') {
        Write-Host "MaintenanceWindowMode is '$maintenanceWindowMode' but LCM is set to '$currentConfigurationMode'. Changing LCM to 'ApplyAndAutoCorrect'"
        Set-LcmMode -Mode 'ApplyAndAutoCorrect'
        $lcmModeChanged = 'ApplyAndAutoCorrect'
    }
    elseif ($maintenanceWindowMode -eq 'Monitor' -and $currentConfigurationMode -ne 'ApplyAndMonitor') {
        Write-Host "MaintenanceWindowMode is '$maintenanceWindowMode' but LCM is set to '$currentConfigurationMode'. Changing LCM to 'ApplyAndMonitor'"
        Set-LcmMode -Mode 'ApplyAndMonitor'
        $lcmModeChanged = 'ApplyAndMonitor'
    }
}

if ($inMaintenanceWindow) {
    $doAutoCorrect = Test-StartDscAutoCorrect
    $doRefresh = Test-StartDscRefresh

    if ($doRefresh) {
        Start-Refresh
    }
    else {
        Write-Host "NO ACTION: 'doRefresh' is false, not invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags '5' (Pull and Consistency Check)."
    }

    if ($doAutoCorrect) {
        Start-AutoCorrect
    }
    else {
        Write-Host "NO ACTION: 'doAutoCorrect' is false, not invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags '1' (Consistency Check)."
    }
    
}

Write-Host
if ($lcmModeChanged) {
    Write-Host "Setting LCM back from '$lcmModeChanged' to '$currentConfigurationMode'."
    Set-LcmMode -Mode $currentConfigurationMode
}

Write-Host
if (-not $doAutoCorrect) {
    $doMonitor = Test-StartDscMonitor
    if ($doMonitor) {
        Start-Monitor
    }
    else {
        Write-Host "NO ACTION: 'doMonitor' is false, not invoking Cim Method 'PerformRequiredConfigurationChecks' with Flags '1' (Consistency Check)."
    }
}
else {
    Write-Host "In AutoCorrect mode, skipping Montior"
}

$logItem = [pscustomobject]@{
    CurrentTime = (Get-Date).ToString('M\/d\/yyyy h:m:s tt', [System.Globalization.CultureInfo]::InvariantCulture)
    InMaintenanceWindow = [int]$inMaintenanceWindow
    DoAutoCorrect = [int]$doAutoCorrect
    DoMonitor = [int]$doMonitor
    DoRefresh = [int]$doRefresh

    LastAutoCorrect = $lastAutoCorrect.ToString('M\/d\/yyyy h:m:s tt', [System.Globalization.CultureInfo]::InvariantCulture)
    LastMonitor = $lastMonitor.ToString('M\/d\/yyyy h:m:s tt', [System.Globalization.CultureInfo]::InvariantCulture)
    AutoCorrectInterval = $autoCorrectInterval
    AutoCorrectIntervalOverride = $autoCorrectIntervalOverride
    ConsistencyCheckErrors = $autoCorrectErrors

    MonitorInterval = $monitorInterval
    MonitorErrors = $monitorErrors

    LastRefresh = $lastRefresh.ToString('M\/d\/yyyy h:m:s tt', [System.Globalization.CultureInfo]::InvariantCulture)
    RefreshInterval = $refreshInterval
    RefreshIntervalOverride = $refreshIntervalOverride
    RefreshErrors = $refreshErrors

    MaxLcmRuntime = $maxLcmRuntime
    LcmRuntime = $lcmRuntime

    SendDscTaggingDataError = $sendDscTaggingDataError
    
} | Export-Csv -Path "$path\LcmControllerSummary.csv" -Delimiter ',' -Append -Force

if ($writeTranscripts) {
    Stop-Transcript
}

#------------------------ LcmController.log cleanup ----------------------------------

$pattern = '(\*{22}\r\nWindows PowerShell transcript start\r\n)((.|\r\n)+?)(End time: \d{14}\r\n\*{22})'
$date = (Get-Date) - $logHistoryTimeSpan

$lcmControllerLogContent = Get-Content -Path "$path\LcmController.log" -Raw
$regexMatches = [regex]::Matches($lcmControllerLogContent, $pattern)

$logEntries = $regexMatches | Where-Object {
    [datetime]::ParseExact((($_.Value -split "\n")[-2] -split ' ')[2].Trim(), 'yyyyMMddHHmmss', $null) -gt $date
}

#$logEntries | Group-Object -Property { [datetime]::ParseExact((($_.Value -split "\n")[-2] -split ' ')[2].Trim(),'yyyyMMddHHmmss',$null).ToString('yy MM dd') }

Write-Host "Log file contained $($regexMatches.Count) entries, after cleanup if contains $($logEntries.Count) entries."
$logEntries.Value | Out-File -FilePath "$path\LcmController.log" -Force

#------------------ LcmControllerSummary.csv cleanup ------------------------------

$summaryContent = Import-Csv -Path "$path\LcmControllerSummary.csv" -Delimiter ','
$filteredSummaryContent = $summaryContent | Where-Object { [datetime]$_.CurrentTime -gt $date }

Write-Host "Summary file contained $($summaryContent.Count) entries, after cleanup if contains $($filteredSummaryContent.Count) entries."
$filteredSummaryContent | Export-Csv -Path "$path\LcmControllerSummary.csv" -Delimiter ',' -Force
'@


configuration DscLcmController {
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Monitor', 'AutoCorrect')]
        [string]$MaintenanceWindowMode,

        [Parameter(Mandatory)]
        [timespan]$MonitorInterval,

        [Parameter(Mandatory)]
        [timespan]$AutoCorrectInterval,
        
        [bool]$AutoCorrectIntervalOverride,

        [Parameter(Mandatory)]
        [timespan]$RefreshInterval,
        
        [bool]$RefreshIntervalOverride,

        [Parameter(Mandatory)]
        [timespan]$ControllerInterval,

        [bool]$MaintenanceWindowOverride,

        [timespan]$MaxLcmRuntime = (New-TimeSpan -Days 2),

        [timespan]$LogHistoryTimeSpan = (New-TimeSpan -Days 90),

        [bool]$SendDscTaggingData,

        [bool]$WriteTranscripts
    )

    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName xPSDesiredStateConfiguration
    Import-DscResource -ModuleName ComputerManagementDsc

    xRegistry DscLcmController_MaintenanceWindowMode {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'MaintenanceWindowMode'
        ValueData = $MaintenanceWindowMode
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_MonitorInterval {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'MonitorInterval'
        ValueData = $MonitorInterval       
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_AutoCorrectInterval {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'AutoCorrectInterval'
        ValueData = $AutoCorrectInterval
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_AutoCorrectIntervalOverride {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'AutoCorrectIntervalOverride'
        ValueData = [int]$AutoCorrectIntervalOverride
        ValueType = 'DWord'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_RefreshInterval {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'RefreshInterval'
        ValueData = $RefreshInterval
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_RefreshIntervalOverride {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'RefreshIntervalOverride'
        ValueData = [int]$RefreshIntervalOverride
        ValueType = 'DWord'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_ControllerInterval {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'ControllerInterval'
        ValueData = $ControllerInterval
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_MaintenanceWindowOverride {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'MaintenanceWindowOverride'
        ValueData = [int]$MaintenanceWindowOverride
        ValueType = 'DWord'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_WriteTranscripts {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'WriteTranscripts'
        ValueData = [int]$WriteTranscripts
        ValueType = 'DWord'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_MaxLcmRuntime {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'MaxLcmRuntime'
        ValueData = $MaxLcmRuntime
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }

    xRegistry DscLcmController_LogHistoryTimeSpan {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'LogHistoryTimeSpan'
        ValueData = $LogHistoryTimeSpan
        ValueType = 'String'
        Ensure    = 'Present'
        Force     = $true
    }
    
    xRegistry DscLcmController_SendDscTaggingData {
        Key       = 'HKEY_LOCAL_MACHINE\SOFTWARE\DscLcmController'
        ValueName = 'SendDscTaggingData'
        ValueData = [int]$SendDscTaggingData
        ValueType = 'DWord'
        Ensure    = 'Present'
        Force     = $true
    }

    File DscLcmControllerScript {
        Ensure          = 'Present'
        Type            = 'File'
        DestinationPath = 'C:\ProgramData\Dsc\LcmController\LcmController.ps1'
        Contents        = $dscLcmControllerScript
    }
    
    ScheduledTask DscControllerTask {
        DependsOn          = '[File]DscLcmControllerScript'
        TaskName           = 'DscLcmController'
        TaskPath           = '\DscController'
        ActionExecutable   = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
        ActionArguments    = '-File C:\ProgramData\Dsc\LcmController\LcmController.ps1'
        ScheduleType       = 'Once'
        RepeatInterval     = $ControllerInterval
        RepetitionDuration = 'Indefinitely'
        StartTime          = (Get-Date)
    }  
}