AutomatedLabSharePoint.psm1

$setupConfigFileContent = '<Configuration>
    <Package Id="sts">
        <Setting Id="LAUNCHEDFROMSETUPSTS" Value="Yes"/>
    </Package>

    <Package Id="spswfe">
        <Setting Id="SETUPCALLED" Value="1"/>
    </Package>

    <Logging Type="verbose" Path="%temp%" Template="SharePoint Server Setup(*).log"/>
    <PIDKEY Value="{0}" />
    <Display Level="none" CompletionNotice="no" />
    <Setting Id="SERVERROLE" Value="APPLICATION"/>
    <Setting Id="USINGUIINSTALLMODE" Value="0"/>
    <Setting Id="SETUP_REBOOT" Value="Never" />
    <Setting Id="SETUPTYPE" Value="CLEAN_INSTALL"/>
</Configuration>'


$SharePoint2013InstallScript = {
    param
    (
        [string]
        $Mode = '/unattended'
    )
    $exitCode = (Start-Process -PassThru -Wait "C:\SPInstall\PrerequisiteInstaller.exe" –ArgumentList "$Mode /SQLNCli:C:\SPInstall\PrerequisiteInstallerFiles\sqlncli.msi `
               /IDFX:C:\SPInstall\PrerequisiteInstallerFiles\Windows6.1-KB974405-x64.msu `
               /IDFX11:C:\SPInstall\PrerequisiteInstallerFiles\MicrosoftIdentityExtensions-64.msi `
               /Sync:C:\SPInstall\PrerequisiteInstallerFiles\Synchronization.msi `
               /AppFabric:C:\SPInstall\PrerequisiteInstallerFiles\WindowsServerAppFabricSetup_x64.exe `
               /KB2671763:C:\SPInstall\PrerequisiteInstallerFiles\AppFabric1.1-RTM-KB2671763-x64-ENU.exe `
               /MSIPCClient:C:\SPInstall\PrerequisiteInstallerFiles\setup_msipc_x64.msi `
               /WCFDataServices:C:\SPInstall\PrerequisiteInstallerFiles\WcfDataServices.exe `
               /WCFDataServices56:C:\SPInstall\PrerequisiteInstallerFiles\WcfDataServices56.exe"
).ExitCode

    return @{
        ExitCode = $exitCode
        Hostname = $env:COMPUTERNAME
    }
}
$SharePoint2016InstallScript = {
    param
    (
        [string]
        $Mode = '/unattended'
    )
    $exitCode = (Start-Process -PassThru -Wait "C:\SPInstall\PrerequisiteInstaller.exe" –ArgumentList "$Mode /SQLNCli:C:\SPInstall\PrerequisiteInstallerFiles\sqlncli.msi `
    /IDFX11:C:\SPInstall\PrerequisiteInstallerFiles\MicrosoftIdentityExtensions-64.msi `
    /Sync:C:\SPInstall\PrerequisiteInstallerFiles\Synchronization.msi `
    /AppFabric:C:\SPInstall\PrerequisiteInstallerFiles\WindowsServerAppFabricSetup_x64.exe `
    /KB3092423:C:\SPInstall\PrerequisiteInstallerFiles\AppFabric-KB3092423-x64-ENU.exe `
    /MSIPCClient:C:\SPInstall\PrerequisiteInstallerFiles\setup_msipc_x64.exe `
    /WCFDataServices56:C:\SPInstall\PrerequisiteInstallerFiles\WcfDataServices.exe `
    /DotNetFx:C:\SPInstall\PrerequisiteInstallerFiles\NDP462-KB3151800-x86-x64-AllOS-ENU.exe `
    /ODBC:C:\SPInstall\PrerequisiteInstallerFiles\msodbcsql.msi `
    /MSVCRT11:C:\SPInstall\PrerequisiteInstallerFiles\vcredist_64_2012.exe `
    /MSVCRT14:C:\SPInstall\PrerequisiteInstallerFiles\vcredist_64_2015.exe"
).ExitCode

    return @{
        ExitCode = $exitCode
        Hostname = $env:COMPUTERNAME
    }
}
$SharePoint2019InstallScript = {
    param
    (
        [string]
        $Mode = '/unattended'
    )
    $exitCode = (Start-Process -Wait -PassThru "C:\SPInstall\PrerequisiteInstaller.exe" –ArgumentList "$Mode /SQLNCli:C:\SPInstall\PrerequisiteInstallerFiles\sqlncli.msi `
    /IDFX11:C:\SPInstall\PrerequisiteInstallerFiles\MicrosoftIdentityExtensions-64.msi `
    /Sync:C:\SPInstall\PrerequisiteInstallerFiles\Synchronization.msi `
    /AppFabric:C:\SPInstall\PrerequisiteInstallerFiles\WindowsServerAppFabricSetup_x64.exe `
    /KB3092423:C:\SPInstall\PrerequisiteInstallerFiles\AppFabric-KB3092423-x64-ENU.exe `
    /MSIPCClient:C:\SPInstall\PrerequisiteInstallerFiles\setup_msipc_x64.exe `
    /WCFDataServices56:C:\SPInstall\PrerequisiteInstallerFiles\WcfDataServices.exe `
    /DotNet472:C:\SPInstall\PrerequisiteInstallerFiles\NDP472-KB4054530-x86-x64-AllOS-ENU.exe `
    /MSVCRT11:C:\SPInstall\PrerequisiteInstallerFiles\vcredist_64_2012.exe `
    /MSVCRT141:C:\SPInstall\PrerequisiteInstallerFiles\vcredist_64_2017.exe"
).ExitCode

    return @{
        ExitCode = $exitCode
        Hostname = $env:COMPUTERNAME
    }
}

#region Install-LabSharePoint
function Install-LabSharePoint
{
    [CmdletBinding()]
    param
    (
        [switch]
        $CreateCheckPoints
    )

    Write-LogFunctionEntry

    $lab = Get-Lab

    if (-not (Get-LabVM))
    {
        Write-LogFunctionExitWithError -Message 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first'
        return
    }

    $machines = Get-LabVM -Role SharePoint2013, SharePoint2016, SharePoint2019
    $versionGroups = $machines | Group-Object { $_.Roles.Name | Where-Object { $_ -match 'SharePoint\d{4}' } }

    if (-not $machines)
    {
        Write-ScreenInfo -Message "There is no SharePoint server in the lab" -Type Warning
        Write-LogFunctionExit
        return
    }

    foreach ($group in $versionGroups)
    {
        if ($null -eq ($lab.Sources.ISOs | Where-Object { $_.Name -eq $group.Name }))
        {
            Write-ScreenInfo -Message "No ISO was added for $($Group.Name). Please use Add-LabIsoImageDefinition to add it before installing a lab."
            return
        }
    }

    Write-ScreenInfo -Message 'Waiting for machines with SharePoint role to start up' -NoNewline
    Start-LabVM -ComputerName $machines -Wait -ProgressIndicator 15

    # Mount OS ISO for Windows Feature Installation
    Write-ScreenInfo -Message 'Installing required features'
    Install-LabWindowsFeature -ComputerName $machines -FeatureName Net-Framework-Features, Web-Server, Web-WebServer, Web-Common-Http, Web-Static-Content, Web-Default-Doc, Web-Dir-Browsing, Web-Http-Errors, Web-App-Dev, Web-Asp-Net, Web-Net-Ext, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Health, Web-Http-Logging, Web-Log-Libraries, Web-Request-Monitor, Web-Http-Tracing, Web-Security, Web-Basic-Auth, Web-Windows-Auth, Web-Filtering, Web-Digest-Auth, Web-Performance, Web-Stat-Compression, Web-Dyn-Compression, Web-Mgmt-Tools, Web-Mgmt-Console, Web-Mgmt-Compat, Web-Metabase, WAS, WAS-Process-Model, WAS-NET-Environment, WAS-Config-APIs, Web-Lgcy-Scripting, Windows-Identity-Foundation, Server-Media-Foundation, Xps-Viewer -IncludeAllSubFeature -IncludeManagementTools -NoDisplay

    $oldMachines = $machines | Where-Object { $_.OperatingSystem.Version -lt 10 }
    if ($Null -ne $oldMachines)
    {
        # Application Server is deprecated in 2016+, despite the SharePoint documentation stating otherwise
        Install-LabWindowsFeature -ComputerName $oldMachines -FeatureName Application-Server, AS-Web-Support, AS-TCP-Port-Sharing, AS-WAS-Support, AS-HTTP-Activation, AS-TCP-Activation, AS-Named-Pipes, AS-Net-Framework -IncludeManagementTools -IncludeAllSubFeature -NoDisplay
    }

    Restart-LabVM -ComputerName $machines -Wait

    # Mount SharePoint ISO
    Dismount-LabIsoImage -ComputerName $machines -SupressOutput

    $jobs = foreach ($group in $versionGroups)
    {
        foreach ($machine in $group.Group)
        {
            $spImage = Mount-LabIsoImage -ComputerName $machine -IsoPath ($lab.Sources.ISOs | Where-Object { $_.Name -eq $group.Name }).Path -PassThru
            Invoke-LabCommand -ComputerName $machine -ActivityName "Copy SharePoint Installation Files" -ScriptBlock {
                Copy-Item -Path "$($spImage.DriveLetter)\" -Destination "C:\SPInstall\" -Recurse
                if ((Test-Path -Path 'C:\SPInstall\prerequisiteinstallerfiles') -eq $false)
                {
                    $null = New-Item -Path 'C:\SPInstall\prerequisiteinstallerfiles' -ItemType Directory
                }
            } -Variable (Get-Variable -Name spImage) -AsJob -PassThru
        }
    }

    Wait-LWLabJob -Job $jobs -NoDisplay

    foreach ($thing in @('cppredist32_2012', 'cppredist64_2012', 'cppredist32_2015', 'cppredist64_2015', 'cppredist32_2017', 'cppredist64_2017'))
    {
        $fName = $thing -replace '(cppredist)(\d\d)_(\d{4})', 'vcredist_$2_$3.exe'
        Get-LabInternetFile -Uri (Get-LabConfigurationItem -Name $thing) -Path $labsources\SoftwarePackages -FileName $fName -NoDisplay
    }

    Copy-LabFileItem -Path $labsources\SoftwarePackages\vcredist_64_2012.exe, $labsources\SoftwarePackages\vcredist_64_2015.exe, $labsources\SoftwarePackages\vcredist_64_2017.exe -ComputerName $machines.Name  -DestinationFolderPath "C:\SPInstall\prerequisiteinstallerfiles"

    # Download and copy Prerequisite Files to server
    Write-ScreenInfo -Message "Downloading and copying prerequisite files to servers"
    foreach ($group in $versionGroups)
    {
        if ($lab.DefaultVirtualizationEngine -eq 'HyperV' -and -not (Test-Path -Path $labsources\SoftwarePackages\$($group.Name)))
        {
            $null = New-Item -ItemType Directory -Path $labsources\SoftwarePackages\$($group.Name)
        }

        foreach ($prereqUri in (Get-LabConfigurationItem -Name "$($group.Name)Prerequisites"))
        {
            $internalUri = New-Object System.Uri($prereqUri)
            $fileName = $internalUri.Segments[$internalUri.Segments.Count - 1]

            $params = @{
                Uri      = $prereqUri
                Path     = "$labsources\SoftwarePackages\$($group.Name)\$($fileName)"
                PassThru = $true
            }

            if ($prereqUri -match '1CAA41C7' -and $group.Name -eq 'SharePoint2013')
            {
                # This little snowflake would like both packages, pretty please
                $params.FileName = 'WcfDataServices56.exe'
            }

            $download = Get-LabInternetFile @params
        }

        Copy-LabFileItem -ComputerName $group.Group -Path $labsources\SoftwarePackages\$($group.Name)\* -DestinationFolderPath "C:\SPInstall\prerequisiteinstallerfiles"

        # Installing Prereqs
        Write-ScreenInfo -Message "Installing prerequisite files for $($group.Name) on server" -Type Verbose
        Invoke-LabCommand -ComputerName $group.Group -NoDisplay -ScriptBlock {
            param ([string] $Script )
            if (-not (Test-Path -Path C:\DeployDebug))
            {
                $null = New-Item -ItemType Directory -Path C:\DeployDebug
            }
            Set-Content C:\DeployDebug\SPPrereq.ps1 -Value $Script

        } -ArgumentList (Get-Variable -Name "$($Group.Name)InstallScript").Value.ToString()
    }

    $instResult = Invoke-LabCommand -PassThru -ComputerName $machines -ActivityName "Install SharePoint (all) Prerequisites" -ScriptBlock { & C:\DeployDebug\SPPrereq.ps1 -Mode '/unattended' }
    $failed = $instResult | Where-Object { $_.ExitCode -notin 0, 3010 }
    if ($null -ne $failed)
    {
        Write-ScreenInfo -Type Error -Message "The following SharePoint servers failed installing prerequisites $($failed.PSComputerName)"
        return
    }

    $rebootRequired = $instResult | Where-Object { $_.ExitCode -eq 3010 }
    while ($null -ne $rebootRequired)
    {
        Write-ScreenInfo -Type Verbose -Message "Some machines require a second pass at installing prerequisites: $($rebootRequired.HostName -join ',')"
        Restart-LabVM -ComputerName $rebootRequired.HostName -Wait
        $instResult = Invoke-LabCommand -PassThru -ComputerName $rebootRequired.HostName -ActivityName "Install $($group.Name) Prerequisites" -ScriptBlock { & C:\DeployDebug\SPPrereq.ps1 -Mode '/unattended /continue' } | Where-Object { $_ -eq 3010 }
        $failed = $instResult | Where-Object { $_.ExitCode -notin 0, 3010 }
        if ($null -ne $failed)
        {
            Write-ScreenInfo -Type Error -Message "The following SharePoint servers failed installing prerequisites $($failed.HostName)"
        }

        $rebootRequired = $instResult | Where-Object { $_.ExitCode -eq 3010 }
    }

    # Install SharePoint binaries
    Write-ScreenInfo -Message "Installing SharePoint binaries on server"
    Restart-LabVM -ComputerName $machines -Wait

    $jobs = foreach ($group in $versionGroups)
    {
        $productKey = Get-LabConfigurationItem -Name "$($group.Name)Key"
        $configFile = $setupConfigFileContent -f $productKey
        Invoke-LabCommand -ComputerName $group.Group -ActivityName "Install SharePoint $($group.Name)" -ScriptBlock {
            Set-Content -Force -Path C:\SPInstall\files\al-config.xml -Value $configFile
            $null = Start-Process -Wait "C:\SPInstall\setup.exe" –ArgumentList "/config C:\SPInstall\files\al-config.xml"
            Set-Content C:\DeployDebug\SPInst.cmd -Value 'C:\SPInstall\setup.exe /config C:\SPInstall\files\al-config.xml'
            Get-ChildItem -Path (Join-Path ([IO.Path]::GetTempPath()) 'SharePoint Server Setup*') | Get-Content
        } -Variable (Get-Variable -Name configFile) -AsJob -PassThru
    }

    Write-ScreenInfo -Message "Waiting for SharePoint role to complete installation" -NoNewLine
    Wait-LWLabJob -Job $jobs -NoDisplay

    foreach ($job in $jobs)
    {
        $jobResult = (Receive-Job -Job $job -Wait -AutoRemoveJob)
        Write-ScreenInfo -Type Verbose -Message "Installation result $jobResult"
    }
}
#endregion Install-LabSharePoint