DSCResources/MSFT_xScriptResource/MSFT_xScriptResource.psm1

data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
SetScriptWhatIfMessage=Executing the SetScript with the user supplied credential
InValidResultFromGetScriptError=Failure to get the results from the script in a hash table format.
InValidResultFromTestScriptError=Failure to get a valid result from the execution of TestScript. The Test script should return True or False.
ScriptBlockProviderScriptExecutionFailureError=Failure to successfully execute the script.
GetTargetResourceStartVerboseMessage=Begin executing Get Script.
GetTargetResourceEndVerboseMessage=End executing Get Script.
SetTargetResourceStartVerboseMessage=Begin executing Set Script.
SetTargetResourceEndVerboseMessage=End executing Set Script.
TestTargetResourceStartVerboseMessage=Begin executing Test Script.
TestTargetResourceEndVerboseMessage=End executing Test Script.
ExecutingScriptMessage=Executing Script: {0}
 
'@

}

# Commented-out until more languages are supported
# Import-LocalizedData LocalizedData -filename MSFT_xScriptResource.strings.psd1

# The Get-TargetResource cmdlet is used to fetch the desired state of the DSC managed node through a powershell script.
# This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the
# DSC managed node). The result of the script execution is in the form of a hashtable containing all the inormation
# gathered from the GetScript execution.
function Get-TargetResource 
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
     param 
     (         
       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]
       $GetScript,
  
       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]$SetScript,

       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]
       $TestScript,

       [Parameter(Mandatory=$false)]
       [System.Management.Automation.PSCredential] 
       $Credential
     )

    $getTargetResourceResult = $null;

    $getTargetResourceStartVerboseMessage = $($LocalizedData.GetTargetResourceStartVerboseMessage);
    Write-Debug -Message $getTargetResourceStartVerboseMessage;
 
    $script = [ScriptBlock]::Create($GetScript);
    $parameters = $psboundparameters.Remove("GetScript");
    $psboundparameters.Add("ScriptBlock", $script);

    $parameters = $psboundparameters.Remove("SetScript");
    $parameters = $psboundparameters.Remove("TestScript");

    $scriptResult = ScriptExecutionHelper @psboundparameters;
  
    $scriptResultAsErrorRescord = $scriptResult -as [System.Management.Automation.ErrorRecord]
    if($null -ne $scriptResultAsErrorRescord)
    {
        $PSCmdlet.ThrowTerminatingError($scriptResultAsErrorRescord);
    }

    $scriptResultAsHasTable = $scriptResult -as [hashtable]

    if($null -ne $scriptResultAsHasTable)
    {
        $getTargetResourceResult = $scriptResultAsHasTable ;
    }
    else
    {
        # Error message indicating failure to get valid hashtable as the result of the Get script execution.
        $errorId = "InValidResultFromGetScript"; 
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult;
        $exception = New-Object System.InvalidOperationException $($LocalizedData.InValidResultFromGetScriptError); 
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }

    $getTargetResourceEndVerboseMessage = $($LocalizedData.GetTargetResourceEndVerboseMessage);
    Write-Debug -Message $getTargetResourceEndVerboseMessage;

    $getTargetResourceResult;
}


# The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script.
# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the
# DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript,
# the SetScript notifies the PS Infrasturcure by setting the variable $DSCMachineStatus.IsRestartRequired to $true.
function Set-TargetResource 
{
    [CmdletBinding(SupportsShouldProcess=$true)]
     param 
     (       
       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]
       $SetScript,

       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]
       $GetScript,

       [Parameter(Mandatory=$false)]
       [System.Management.Automation.PSCredential] 
       $Credential,

       [parameter(Mandatory = $true)]
       [ValidateNotNullOrEmpty()]
       [string]
       $TestScript

 )

    $setscriptmessage = '$SetScript:' + $SetScript
    $testscriptmessage = '$TestScript:' + $TestScript
    if ($pscmdlet.ShouldProcess($($LocalizedData.SetScriptWhatIfMessage))) 
    {
        $setTargetResourceStartVerboseMessage = $($LocalizedData.SetTargetResourceStartVerboseMessage);
        Write-Debug -Message $setTargetResourceStartVerboseMessage;

        $script = [ScriptBlock]::Create($SetScript);
        $parameters = $psboundparameters.Remove("SetScript");
        $psboundparameters.Add("ScriptBlock", $script);

        $parameters = $psboundparameters.Remove("GetScript");
        $parameters = $psboundparameters.Remove("TestScript");

        $scriptResult = ScriptExecutionHelper @psboundparameters ;

        $scriptResultAsErrorRescord = $scriptResult -as [System.Management.Automation.ErrorRecord]
        if($null -ne $scriptResultAsErrorRescord)
        {
            $PSCmdlet.ThrowTerminatingError($scriptResultAsErrorRescord);
        }
        
        $setTargetResourceEndVerboseMessage = $($LocalizedData.SetTargetResourceEndVerboseMessage);
        Write-Debug -Message $setTargetResourceEndVerboseMessage; 
    }
}


# The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script.
# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the
# DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state
# or else false should be returned.
function Test-TargetResource 
{
    [OutputType([Boolean])]
    param 
    (       
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $TestScript,
  
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$SetScript,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$GetScript,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential] 
        $Credential
    )
    $testTargetResourceResult = $false;

    $testTargetResourceStartVerboseMessage = $($LocalizedData.TestTargetResourceStartVerboseMessage);
    Write-Debug -Message $testTargetResourceStartVerboseMessage;

    $script = [ScriptBlock]::Create($TestScript);
    $parameters = $psboundparameters.Remove("TestScript");
    $psboundparameters.Add("ScriptBlock", $script);

    $parameters = $psboundparameters.Remove("GetScript");
    $parameters = $psboundparameters.Remove("SetScript");
     
    $scriptResult = ScriptExecutionHelper @psboundparameters ;

    $scriptResultAsErrorRescord = $scriptResult -as [System.Management.Automation.ErrorRecord]
    if($null -ne $scriptResultAsErrorRescord)
    {
        $PSCmdlet.ThrowTerminatingError($scriptResultAsErrorRescord);
    }

    if($null -eq $scriptResult)
    {
        $errorId = "InValidResultFromTestScript"; 
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult;
        $exception = New-Object System.InvalidOperationException $($LocalizedData.InValidResultFromTestScriptError) ;
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }

    # If the script is returing multiple objects, then we consider the last object to be the result of script execution.
    if($scriptResult.GetType().ToString() -eq 'System.Object[]')
    {
        $reultObject = $scriptResult[$scriptResult.Length -1];
    }
    else
    {
        $reultObject = $scriptResult;
    }

    if(($null -ne $reultObject) -and 
       (($reultObject -eq $true) -or ($reultObject -eq $false)))
    {
        $testTargetResourceResult = $reultObject;
    }
    else
    {
        $errorId = "InValidResultFromTestScript"; 
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult;
        $exception = New-Object System.InvalidOperationException $($LocalizedData.InValidResultFromTestScriptError) ;
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }

    $testTargetResourceEndVerboseMessage = $($LocalizedData.TestTargetResourceEndVerboseMessage);
    Write-Debug -Message $testTargetResourceEndVerboseMessage;

    $testTargetResourceResult;
}


function ScriptExecutionHelper 
{
    param 
    (
        [ScriptBlock] 
        $ScriptBlock,
    
        [System.Management.Automation.PSCredential] 
        $Credential
    )

    $scriptExecutionResult = $null;

    try
    {

        $executingScriptMessage = $($LocalizedData.ExecutingScriptMessage) -f ${ScriptBlock} ;
        Write-Debug -Message $executingScriptMessage;

       if($null -ne $Credential)
       {
          $scriptExecutionResult = Invoke-Command -ScriptBlock $ScriptBlock -ComputerName . -Credential $Credential
       }
       else
       {
          $scriptExecutionResult = &$ScriptBlock;
       }
        $scriptExecutionResult;
    }
    catch
    {
        # Surfacing the error thrown by the execution of Get/Set/Test script.
        $_;
    }
}

Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource