DSCResources/Grani_ScriptOnOSVersion/Grani_ScriptOnOSVersion.psm1

#region Initialize

function Initialize {
    try {
        # Enum for Ensure
        Add-Type -TypeDefinition @"
        public enum EnsureType
        {
            Present,
            Absent
        }
"@
 -ErrorAction SilentlyContinue;

    }
    catch {
        if ($_.FullyQualifiedErrorId -match "TYPE_ALREADY_EXISTS") {
            # enough. I know already imported this type. SHOULD NOT ERROR
            return
        }
        throw
    }
}

. Initialize;

#endregion

#region Message Definition

Data VerboseMessages {
    ConvertFrom-StringData -StringData @"
        BeginGetTarget = Check Platform&OSVersion indicate to run script.
"@

}

Data DebugMessages {
    ConvertFrom-StringData -StringData @"
        PlatformCheck = Checking status for Platform. Current : {0}, Desired {1}
        OSVersionCheck = Checking status for OSVersion. Current : {0}, Desired {1}
        TestScriptCheck = Checking status for TestScript. TestScript : {0}
"@

}

Data ErrorMessages {
    ConvertFrom-StringData -StringData @"
        InvalidTestScript = TestScript should return Boolean but detected return value as null with TestScript : {0}
        UnexpectedWhenValue = Unexpected When Parameter value detected : {0}
"@

}

#endregion

#region *-TargetResource

function Get-TargetResource {
    [OutputType([System.Collections.Hashtable])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [System.String]$Key,

        [System.String]$SetScript,

        [System.String]$TestScript,

        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnPlatform,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnVersion,

        [parameter(Mandatory = $true)]
        [ValidateSet("LessThan", "LessThanEqual", "Equal", "NotEqual", "GreaterThan", "GreaterThanEqual")]
        [System.String]$When
    )

    $returnValue = @{
        Key               = $Key
        SetScript         = $SetScript
        TestScript        = $TestScript
        Credential        = New-CimInstance -ClassName MSFT_Credential -Property @{Username = [string]$Credential.UserName; Password = [string]$null} -Namespace root/microsoft/windows/desiredstateconfiguration -ClientOnly
        ExecuteOnPlatform = $ExecuteOnPlatform
        ExecuteOnVersion  = $ExecuteOnVersion
        When              = $When
    }

    Write-Verbose $VerboseMessages.BeginGetTarget;

    try {
        # System.Environment.OSVersion is only for Full.NET CLR.
        # if DSC is implemented in .NET Core, should use System.Runtime.InteropServices.RuntimeInformation.OSDescription instead.
        $currentVersion = [System.Environment]::OSVersion
        $executeOnVersion = [Version]::Parse($ExecuteOnVersion)

        # Platform check
        Write-Debug ($DebugMessages.PlatformCheck -f $currentVersion.Platform, $ExecuteOnPlatform)
        if ($currentVersion.Platform -ne $ExecuteOnPlatform) {
            $returnValue.Ensure = [EnsureType]::Absent
            return $returnValue
        }

        # OSVersion check
        Write-Debug ($DebugMessages.OSVersionCheck -f $currentVersion.Version, $executeOnVersion)
        switch ($When) {
            "LessThan" {
                if (!($currentVersion.Version -lt $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            "LessThanEqual" {
                if (!($currentVersion.Version -le $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            "Equal" {
                if (!($currentVersion.Version -eq $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            "NotEqual" {
                if (!($currentVersion.Version -ne $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            "GreaterThan" {
                if (!($currentVersion.Version -gt $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            "GreaterThanEqual" {
                if (!($currentVersion.Version -ge $executeOnVersion)) {
                    $returnValue.Ensure = [EnsureType]::Absent
                    return $returnValue
                }
            }
            Default {
                Write-Error ($ErrorMessages.UnexpectedWhenValue -f $_);
            }
        }

        # TestScript Check
        Write-Debug ($DebugMessages.TestScriptCheck -f $TestScript)
        $testValid = ExecuteTestScriptBlock -ScriptBlockString $TestScript -Credential $Credential
        if ($null -eq $testValid) {
            $errorId = "InvalidTestScript"
            $errorMessage = $ErrorMessages.InvalidTestScript -f $TestScript
            ThrowInvalidDataException -ErrorId $errorId -ErrorMessage $errorMessage
        }
        $returnValue.Ensure = if ($testValid) { [EnsureType]::Present } else { [EnsureType]::Absent }
        return $returnValue;
    }
    catch {
        Write-Error $_;
    }
}

function Set-TargetResource {
    [OutputType([Void])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [System.String]$Key,

        [System.String]$SetScript,

        [System.String]$TestScript,

        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnPlatform,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnVersion,

        [parameter(Mandatory = $true)]
        [ValidateSet("LessThan", "LessThanEqual", "Equal", "NotEqual", "GreaterThan", "GreaterThanEqual")]
        [System.String]$When
    )

    ExecuteScriptBlock -ScriptBlockString $SetScript -Credential $Credential
}


function Test-TargetResource {
    [OutputType([System.Boolean])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [System.String]$Key,

        [System.String]$SetScript,

        [System.String]$TestScript,

        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnPlatform,

        [parameter(Mandatory = $true)]
        [System.String]$ExecuteOnVersion,

        [parameter(Mandatory = $true)]
        [ValidateSet("LessThan", "LessThanEqual", "Equal", "NotEqual", "GreaterThan", "GreaterThanEqual")]
        [System.String]$When
    )

    $param = @{
        Key               = $Key
        SetScript         = $SetScript
        TestScript        = $TestScript
        Credential        = $Credential
        ExecuteOnPlatform = $ExecuteOnPlatform
        ExecuteOnVersion  = $ExecuteOnVersion
        When              = $When
    }

    return (Get-TargetResource @param).Ensure -eq [EnsureType]::Present
}

#endregion

# ScriptBlock Execute Helper
function ExecuteScriptBlock {
    [OutputType([Void])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $false)]
        [System.String]$ScriptBlockString = [string]::Empty,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty
    )

    if ($ScriptBlockString -eq [string]::Empty) { return; }

    try {
        $scriptBlock = [ScriptBlock]::Create($ScriptBlockString).GetNewClosure()
        if ($Credential -eq [PSCredential]::Empty) {
            Write-Debug ($debugMessage.ExecuteScriptBlock -f $ScriptBlockString)
            $scriptBlock.Invoke() | Out-String -Stream | Write-Debug
        }
        else {
            Write-Debug ($debugMessage.ExecuteScriptBlockWithCredential -f $ScriptBlockString)
            Invoke-Command -ScriptBlock $scriptBlock -Credential $Credential -ComputerName . | Out-String -Stream | Write-Debug
        }
    }
    catch {
        Write-Debug ($exceptionMessage.ScriptBlockException -f $ScriptBlockString)
        throw $_
    }
}

function ExecuteTestScriptBlock {
    [OutputType([Bool])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $false)]
        [System.String]$ScriptBlockString = [string]::Empty,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty
    )

    if ($ScriptBlockString -eq [string]::Empty) { return $true; }

    try {
        $scriptBlock = [ScriptBlock]::Create($ScriptBlockString).GetNewClosure()
        if ($Credential -eq [PSCredential]::Empty) {
            Write-Debug ($debugMessage.ExecuteScriptBlock -f $ScriptBlockString)
            return $scriptBlock.Invoke()
        }
        else {
            Write-Debug ($debugMessage.ExecuteScriptBlockWithCredential -f $ScriptBlockString)
            return Invoke-Command -ScriptBlock $scriptBlock -Credential $Credential -ComputerName .
        }
    }
    catch {
        Write-Debug ($exceptionMessage.ScriptBlockException -f $ScriptBlockString)
        throw $_
    }
}

# Exception Helper
function ThrowInvalidDataException {
    [OutputType([Void])]
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [System.String]$ErrorId,

        [parameter(Mandatory = $true)]
        [System.String]$ErrorMessage
    )

    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData
    $exception = New-Object System.InvalidOperationException $ErrorMessage
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null
    throw $errorRecord
}

Export-ModuleMember -Function *-TargetResource