DSCResources/MSFT_xExchInstall/MSFT_xExchInstall.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCDscExamplesPresent", "")]
[CmdletBinding()]
param()

function Get-TargetResource 
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        $Path,

        [Parameter(Mandatory=$true)]
        [System.String]
        $Arguments,
        
        [Parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )
    
    #Load helper module
    Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xExchangeCommon.psm1" -Verbose:0

    LogFunctionEntry -Parameters @{"Path" = $Path; "Arguments" = $Arguments} -VerbosePreference $VerbosePreference

    $returnValue = @{
        Path = $Path
        Arguments = $Arguments
    }

    $returnValue
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        $Path,

        [Parameter(Mandatory=$true)]
        [System.String]
        $Arguments,
        
        [Parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xExchangeCommon.psm1" -Verbose:0

    LogFunctionEntry -Parameters @{"Path" = $Path; "Arguments" = $Arguments} -VerbosePreference $VerbosePreference

    $installStatus = GetInstallStatus -Arguments $Arguments

    if ($installStatus.ShouldStartInstall -eq $true)
    {
        #Check if WSMan needs to be configured, as it will require an immediate reboot
        $needReboot = CheckWSManConfig

        if ($needReboot -eq $true)
        {
            Write-Warning "Server needs a reboot before the installation of Exchange can begin."
            return
        }

        Write-Verbose "Initiating Exchange Setup. Command: $($Path) $($Arguments)"

        StartScheduledTask -Path "$($Path)" -Arguments "$($Arguments)" -Credential $Credential -TaskName "Install Exchange" -VerbosePreference $VerbosePreference

        $detectedExsetup = $false

        Write-Verbose "Waiting up to 60 seconds before exiting to give time for ExSetup.exe to start"

        for ($i = 0; $i -lt 60; $i++)
        {
            if ($null -eq (Get-Process -Name ExSetup -ErrorAction SilentlyContinue))
            {
                Start-Sleep -Seconds 1
            }
            else
            {
                Write-Verbose "Detected that ExSetup.exe is running"
                $detectedExsetup = $true
                break
            }
        }

        if ($detectedExsetup -eq $false)
        {
            throw "Waited 60 seconds, but was unable to detect that ExSetup.exe was started"
        }

        #Now wait for setup to finish
        while ($null -ne (Get-Process -Name ExSetup -ErrorAction SilentlyContinue))
        {
            Write-Verbose "Setup is still running at $([DateTime]::Now). Sleeping for 1 minute."
            Start-Sleep -Seconds 60
        }
    }
    else
    {
        if ($installStatus.SetupComplete)
        {
            Write-Verbose "Exchange setup has already successfully completed."
        }
        else
        {
            Write-Verbose "Exchange setup is already in progress."
        }         
    }
}

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

        [Parameter(Mandatory=$true)]
        [System.String]
        $Arguments,
        
        [Parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    Import-Module "$((Get-Item -LiteralPath "$($PSScriptRoot)").Parent.Parent.FullName)\Misc\xExchangeCommon.psm1" -Verbose:0

    LogFunctionEntry -Parameters @{"Path" = $Path; "Arguments" = $Arguments} -VerbosePreference $VerbosePreference

    $installStatus = GetInstallStatus -Arguments $Arguments

    if ($installStatus.ShouldStartInstall -eq $true)
    {
        if($installStatus.ShouldInstallLanguagePack -eq $true)
        {
            Write-Verbose "Language pack will be installed"
        }
        else
        {
            Write-Verbose "Exchange is either not installed, or a previous install only partially completed."
        }
    }
    else
    {
        if ($installStatus.SetupComplete)
        {
            Write-Verbose "Exchange setup has already successfully completed."
        }
        else
        {
            Write-Verbose "Exchange setup is already in progress."
        }
    }

    return (!($installStatus.ShouldStartInstall))
}

#Checks for the exact status of Exchange setup and returns the results in a Hashtable
function GetInstallStatus
{
    param
    (
        $Arguments
    )

    $shouldStartInstall = $false

    $shouldInstallLanguagePack = ShouldInstallLanguagePack -Arguments $Arguments
    $setupRunning = IsSetupRunning
    $setupComplete = IsSetupComplete
    $exchangePresent = IsExchangePresent
    $setupPartiallyComplete = IsSetupPartiallyCompleted

    if ($setupRunning -eq $true -or $setupComplete -eq $true)
    {
        if($shouldInstallLanguagePack -eq $true -and $setupComplete -eq $true)
        {
            $shouldStartInstall = $true
        }
        else
        {
            #Do nothing. Either Install is already running, or it's already finished successfully
        }
    }
    elseif ($exchangePresent -eq $false -or $setupPartiallyComplete -eq $true)
    {
        $shouldStartInstall = $true
    }

    $returnValue = @{
        ShouldInstallLanguagePack = $shouldInstallLanguagePack
        SetupRunning = $setupRunning
        SetupComplete = $setupComplete
        ExchangePresent = $exchangePresent
        SetupPartiallyComplete = $setupPartiallyComplete
        ShouldStartInstall = $shouldStartInstall
    }

    $returnValue
}

#Check for missing registry keys that may cause Exchange setup to try to restart WinRM mid setup , which will in turn cause the DSC resource to fail
#If any required keys are missing, configure WinRM, then force a reboot
function CheckWSManConfig
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    ()

    $needReboot = $false

    $wsmanKey = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN" -ErrorAction SilentlyContinue

    if ($null -ne $wsmanKey)
    {
        if ($null -eq $wsmanKey.UpdatedConfig)
        {
            $needReboot = $true

            Write-Verbose "Value 'UpdatedConfig' missing from registry key HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN. Running: winrm i restore winrm/config"

            Set-Location "$($env:windir)\System32\inetsrv"
            Invoke-Expression -Command "winrm i restore winrm/config" | Out-Null

            Write-Verbose "Machine needs to be rebooted before Exchange setup can proceed"

            $global:DSCMachineStatus = 1
        }
    }
    else
    {
        throw "Unable to find registry key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN"
    }

    return $needReboot
}

function ShouldInstallLanguagePack
{
    param
    (
        [System.String]
        $Arguments
    )

    if($Arguments -match '(?<=/AddUMLanguagePack:)(([a-z]{2}-[A-Z]{2},?)+)(?=\s)')
    {
        $Cultures = $Matches[0]
        Write-Verbose "AddUMLanguagePack parameters detected: $Cultures"
        $Cultures = $Cultures -split ","

        foreach($Culture in $Cultures)
        {
            if((IsUMLanguagePackInstalled -Culture $Culture) -eq $false)
            {
                Write-Verbose "UM Language Pack: $Culture is not installed"
                return $true
            }
        }
    }
    return $false
}

Export-ModuleMember -Function *-TargetResource