Lib/BootStrap.ps1

function NewBootStrap {
<#
    .SYNOPSIS
        Creates a lab DSC BootStrap script block.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ScriptBlock])]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $CoreCLR
    )
    process {
        $coreCLRScriptBlock = {
## Lability CoreCLR DSC Bootstrap
$VerbosePreference = 'Continue';

## TODO: Need to find a Nano equivalent of CertUtil.exe!

<#CustomBootStrapInjectionPoint#>

if (Test-Path -Path "$env:SystemDrive\BootStrap\localhost.meta.mof") {
    Set-DscLocalConfigurationManager -Path "$env:SystemDrive\BootStrap\" -Verbose;
}

if (Test-Path -Path "$env:SystemDrive\BootStrap\localhost.mof") {
    while ($true) {
        ## Replay the configuration until the LCM bloody-well takes it!
        try {
            Start-DscConfiguration -Path "$env:SystemDrive\Bootstrap\" -Force -Wait -Verbose -ErrorAction Stop;
            break;
        }
        catch {
            Write-Error -Message $_;
            Start-Sleep -Seconds 5;
        }
    } #end while
} #end if localhost.mof
} #end CoreCLR bootstrap scriptblock

        $scriptBlock = {
## Lability DSC Bootstrap
$VerbosePreference = 'Continue';
$DebugPreference = 'Continue';
$transcriptPath = '{0}\BootStrap\Bootstrap-{1}.log' -f $env:SystemDrive, (Get-Date).ToString('yyyyMMdd-hhmmss');
Start-Transcript -Path $transcriptPath -Force;

certutil.exe -addstore -f "Root" "$env:SYSTEMDRIVE\BootStrap\LabRoot.cer";
## Import the .PFX certificate with a blank password
"" | certutil.exe -f -importpfx "$env:SYSTEMDRIVE\BootStrap\LabClient.pfx";

<#CustomBootStrapInjectionPoint#>

## Account for large configurations being "pushed" and increase the default from 500KB to 1024KB (1MB)
Set-Item -Path WSMan:\localhost\MaxEnvelopeSizekb -Value 1024 -Force -Verbose;

if (Test-Path -Path "$env:SystemDrive\BootStrap\localhost.meta.mof") {
    Set-DscLocalConfigurationManager -Path "$env:SystemDrive\BootStrap\" -Verbose;
}

$localhostMofPath = "$env:SystemDrive\BootStrap\localhost.mof";
if (Test-Path -Path $localhostMofPath) {
    if ($PSVersionTable.PSVersion.Major -eq 4) {
        ## Convert the .mof to v4 compatible - credit to Mike Robbins
        ## http://mikefrobbins.com/2014/10/30/powershell-desired-state-configuration-error-undefined-property-configurationname/
        $mof = Get-Content -Path $localhostMofPath;
        $mof -replace '^\sName=.*;$|^\sConfigurationName\s=.*;$' | Set-Content -Path $localhostMofPath -Encoding Unicode -Force;
    }
    while ($true) {
        ## Replay the configuration until the LCM bloody-well takes it!
        try {
            if (Test-Path -Path "$env:SystemRoot\System32\Configuration\Pending.mof") {
                Start-DscConfiguration -UseExisting -Force -Wait -Verbose -ErrorAction Stop;
                break;
            }
            else {
                Start-DscConfiguration -Path "$env:SystemDrive\Bootstrap\" -Force -Wait -Verbose -ErrorAction Stop;
                break;
            }
        }
        catch {
            Write-Error -Message $_;
            ## SIGH. Try restarting WMI..
            if (-not ($interation % 10)) {
                ## SIGH. Try removing the configuration and restarting WMI..
                Remove-DscConfigurationDocument -Stage Current,Pending,Previous -Force;
                Restart-Service -Name Winmgmt -Force;
            }
            Start-Sleep -Seconds 5;
            $interation++;
        }
    } #end while
} #end if localhost.mof

Stop-Transcript;
} #end bootstrap scriptblock

        if ($CoreCLR) {
            return $coreCLRScriptBlock;
        }
        else {
            return $scriptBlock;
        }
    } #end process
} #end function NewBootStrap

function SetSetupCompleteCmd {
<#
    .SYNOPSIS
        Creates a lab BootStrap script block.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ScriptBlock])]
    param (
        ## Destination SetupComplete.cmd directory path.
        [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()]
        [System.String] $Path,

        ## Is a CoreCLR VM. The bootstrapping via Powershell.exe in the CoreCLR doesn't work in its current format, i.e. with Nano Server
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $CoreCLR
    )
    process {
        [ref] $null = NewDirectory -Path $Path;
        $setupCompletePath = Join-Path -Path $Path -ChildPath 'SetupComplete.cmd';
        if ($CoreCLR) {
            WriteVerbose -Message $localized.UsingCoreCLRSetupComplete;
            $setupCompleteCmd = @"
schtasks /create /tn "BootStrap" /tr "cmd.exe /c """Powershell.exe -Command %SYSTEMDRIVE%\BootStrap\BootStrap.ps1""" > %SYSTEMDRIVE%\BootStrap\BootStrap.log" /sc "Once" /sd "01/01/2099" /st "00:00" /ru "System"
schtasks /run /tn "BootStrap"
"@

        }
        else {
            WriteVerbose -Message $localized.UsingDefaultSetupComplete;
            $setupCompleteCmd = 'Powershell.exe -NoProfile -ExecutionPolicy Bypass -NonInteractive -File "%SYSTEMDRIVE%\BootStrap\BootStrap.ps1"';
        }
        Set-Content -Path $setupCompletePath -Value $setupCompleteCmd -Encoding Ascii -Force;
    } #end process
} #end function SetSetupCompleteCmd

function SetBootStrap {
<#
    .SYNOPSIS
        Writes the lab BootStrap.ps1 file to the target directory.
#>

    [CmdletBinding()]
    param (
        ## Destination Bootstrap directory path.
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.String] $Path,

        ## Custom bootstrap script
        [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()]
        [System.String] $CustomBootStrap,

        ## Is a CoreCLR VM. The PowerShell switches are different in the CoreCLR, i.e. Nano Server
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $CoreCLR
    )
    process {
        [ref] $null = NewDirectory -Path $Path;
        $bootStrapPath = Join-Path -Path $Path -ChildPath 'BootStrap.ps1';
        $bootStrap = (NewBootStrap -CoreCLR:$CoreCLR).ToString();
        if ($CustomBootStrap) {
            $bootStrap = $bootStrap -replace '<#CustomBootStrapInjectionPoint#>', $CustomBootStrap;
        }
        Set-Content -Path $bootStrapPath -Value $bootStrap -Encoding UTF8 -Force;
    } #end process
} #end function SetBootStrap

function ResolveCustomBootStrap {
<#
    .SYNOPSIS
        Resolves the media and node custom bootstrap, using the specified CustomBootstrapOrder
#>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        ## Custom bootstrap order
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateSet('ConfigurationFirst','ConfigurationOnly','Disabled','MediaFirst','MediaOnly')]
        [System.String] $CustomBootstrapOrder,

        ## Node/configuration custom bootstrap script
        [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()]
        [System.String] $ConfigurationCustomBootStrap,

        ## Media custom bootstrap script
        [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()]
        [System.String[]] $MediaCustomBootStrap
    )
    begin {
        if ([System.String]::IsNullOrWhiteSpace($ConfigurationCustomBootStrap)) {
            $ConfigurationCustomBootStrap = "";
        }
        ## Convert the string[] into a multi-line string
        if ($MediaCustomBootstrap) {
            $mediaBootstrap = [System.String]::Join("`r`n", $MediaCustomBootStrap);
        }
        else {
            $mediaBootstrap = "";
        }
    } #end begin
    process {
        switch ($CustomBootstrapOrder) {
            'ConfigurationFirst' {
                $bootStrap = "{0}`r`n{1}" -f $ConfigurationCustomBootStrap, $mediaBootstrap;
            }
            'ConfigurationOnly' {
                $bootStrap = $ConfigurationCustomBootStrap;
            }
            'MediaFirst' {
                $bootStrap = "{0}`r`n{1}" -f $mediaBootstrap, $ConfigurationCustomBootStrap;
            }
            'MediaOnly' {
                $bootStrap = $mediaBootstrap;
            }
            Default {
                #Disabled
            }
        } #end switch
        return $bootStrap;
    } #end process
} #end function ResolveCustomBootStrap