qbo4.WebServer.ps1

<#
Manage installation of IIS and WebDeploy to prepare a web or application server for qbo3 installation.
#>


function Install-qboWebServer {
    <#
    .SYNOPSIS
        Installs the IIS features qbo3 requires to run, using the ServerManager Powershell cmdlet 'Install-WindowsFeature'.
        This only works on Windows Server.
    .INPUTS
        features: Array of features required by qbo3. Only use this if you need to override the default value.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [string[]]$Features,
        [Parameter(Position=1)]
        [ValidateSet('3.6','4')]
        [string]$Version="4",
        [Parameter(Position=2)]
        [bool]$Force=$true
    )
    $os = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType
    if ($os -eq 1) {
        Install-qboWebServerWindowsClient 
    } else {
        Install-qboWebServerWindowsServer 
    }
    Install-qboWebDeploy $Version $Force
}

function Install-qboWebServerWindowsServer {
    <#
    .SYNOPSIS
        Installs the IIS features qbo3 requires to run, using the ServerManager Powershell cmdlet 'Install-WindowsFeature'.
        This only works on Windows Server.
    .INPUTS
        features: Array of features required by qbo3. Only use this if you need to override the default value.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [string[]]$Features = @("Web-WebServer","Web-Common-Http","Web-Default-Doc","Web-Http-Errors","Web-Static-Content",
            "Web-Http-Redirect","Web-Health","Web-Http-Logging","Web-Custom-Logging","Web-Log-Libraries","Web-ODBC-Logging",
            "Web-Stat-Compression","Web-Dyn-Compression","Web-Security","Web-Basic-Auth","Web-CertProvider","Web-Cert-Auth",
            "Web-Url-Auth","Web-App-Dev","Web-Net-Ext","Web-Net-Ext45","Web-Asp-Net","Web-Asp-Net45","Web-ISAPI-Ext" ,"Web-ISAPI-Filter",
            "Web-Includes","Web-WebSockets","Web-Mgmt-Tools","Web-Scripting-Tools","Web-Mgmt-Service")
    )
    Install-WindowsFeature -Name $Features
}

function Install-qboWebServerWindowsClient {
    <#
    .SYNOPSIS
        Installs the IIS features qbo3 requires to run, using the DSIM Powershell cmdlet 'Enable-WindowsOptionalFeature'.
        This works on Windows 10 (non-server machines).
    .INPUTS
        features: Array of features required by qbo3. Only use this if you need to override the default value.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [string[]]$Features = @("IIS-WebServerRole","IIS-WebServer","IIS-CommonHttpFeatures","IIS-HttpErrors","IIS-HttpRedirect",
            "IIS-ApplicationDevelopment","NetFx4Extended-ASPNET45","IIS-NetFxExtensibility","IIS-NetFxExtensibility45","IIS-HealthAndDiagnostics",
            "IIS-HttpLogging","IIS-LoggingLibraries","IIS-RequestMonitor","IIS-HttpTracing","IIS-Security","IIS-URLAuthorization","IIS-RequestFiltering",
            "IIS-IPSecurity","IIS-Performance","IIS-HttpCompressionDynamic","IIS-WebServerManagementTools","IIS-ManagementScriptingTools",
            "IIS-HostableWebCore","IIS-StaticContent","IIS-DefaultDocument","IIS-WebSockets","IIS-ASPNET45","IIS-CGI",
            "IIS-ISAPIExtensions","IIS-ISAPIFilter","IIS-ServerSideIncludes","IIS-CustomLogging","IIS-BasicAuthentication",
            "IIS-HttpCompressionStatic","IIS-ManagementConsole","IIS-ManagementService","IIS-CertProvider","IIS-WindowsAuthentication",
            "IIS-DigestAuthentication","IIS-ClientCertificateMappingAuthentication","IIS-IISCertificateMappingAuthentication",
            "IIS-ODBCLogging")
    )

    foreach ($i in $Features) {
        $f = Get-WindowsOptionalFeature -online -FeatureName $i
        if ($f -eq $null) {
            Write-Warning("This machine does not appear to have the $i feature available.")
        } elseif ($f.State -eq 'Enabled') {
            Write-Verbose("Feature $i already enabled.")
        } else {
            Write-Verbose("Enabling feature $i.")
            Enable-WindowsOptionalFeature -online -FeatureName $i
        }
    }
}

function Install-qboWebDeploy {
    <#
    .SYNOPSIS
        Installs Web Deploy to enable publishing web packages to IIS.
    .INPUTS
        Version: Web Deploy version to install; either 3.6 or 4
        Force: When true, reinstall WebDeploy even if already present.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [ValidateSet('3.6','4')]
        [string]$Version="4",
        [Parameter(Position=1)]
        [bool]$Force=$true
    )
    $versions = @{
        "3.6"="https://download.microsoft.com/download/0/1/D/01DC28EA-638C-4A22-A57B-4CEF97755C6C/WebDeploy_amd64_en-US.msi"; 
        "4"="https://download.visualstudio.microsoft.com/download/pr/e1828da1-907a-46fe-a3cf-f3b9ea1c485c/035860f3c0d2bab0458e634685648385/webdeploy_amd64_en-us.msi"
    }

    $ms = get-service -name 'WMSvc' -erroraction SilentlyContinue
    if ($ms -eq $null) {
        Write-Warning "Web Management Service is not installed. Please run Install-qboWebServer to ensure it's installed."
        return $false
    }
    Write-Verbose "Starting Web Management Service"
    Start-Service -Name "WMSvc"

    $ds = get-service -name 'MsDepSvc' -erroraction SilentlyContinue

    if (($ds -eq $null) -or $Force) {

        $wdpath = "$($env:temp)\wdmsi.msi"
        Write-Verbose "Downloading WebDeploy msi to $($wdpath)"
        $wdmsi = $versions[$Version]
        Invoke-WebRequest $wdmsi -OutFile $wdpath

        Write-Verbose "Installing WebDeploy silently."
        $arguments = "/i `"$wdpath`" /qb ADDLOCAL=ALL /quiet "
        Start-Process msiexec.exe -ArgumentList $arguments -Wait
    } else {
        Write-Verbose "Start Web Deployment Agent Service."
        Start-Service -Name "MsDepSvc"
    }
    return $true
}

function Get-InstalledApps {
    [CmdletBinding()]
    Param()
    if ([IntPtr]::Size -eq 4) {
        $regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    }
    else {
        $regpath = @(
            'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
            'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
        )
    }
    Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }} | Select DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString |Sort DisplayName
}

function Install-qboExecutable {
    <#
    .SYNOPSIS
        Downloads and installs an executable.
    .INPUTS
        Url: Url of the executable to download
        Argument: Argument to pass to executable.
    #>

    [CmdletBinding()]
    Param(
        [string]$Url,
        [string]$Arguments,
        [string]$Program
    )

    if ($Program -ne $null) {
        $app = get-installedapps | where {$_.DisplayName -like $Program}
        if ($app -ne $null) {
            Write-Verbose "$($app.DisplayName) is already installed"
            return
        }
    }

    $installPath = "$($env:temp)\$(New-Guid).exe"
    Write-Verbose "Downloading to $($installPath)"
    Invoke-WebRequest $Url -OutFile $installPath
    if ($Arguments -ne $null) {
        start-process $installPath -ArgumentList $Arguments -wait
    } else {
        start-process $installPath -wait
    }
}

function Install-qboThirdPartyComponents {
    <#
    .SYNOPSIS
        Installs .dotnet runtime, aspnetcore runtime, and Access redistribuable engine.
    .INPUTS
        Url: Url of the executable to download
        Argument: Argument to pass to executable.
    #>

    [CmdletBinding()]
    Param()

    $logfile = "$($env:temp)\dotnet.log"
    # Microsoft .NET Core Runtime - 3.1.9
    # Write-Verbose "Installing dotnet runtime, logging to $logfile"
    # install-qboExecutable -url "https://download.visualstudio.microsoft.com/download/pr/ceff8b33-6f27-425f-957d-91039cf01a9c/312410f11691fae3272f4274f787eb12/dotnet-runtime-3.1.9-win-x64.exe" -Arguments '/install /norestart /quiet /log "$($logfile)"' -Program "Microsoft .NET Core Runtime - 3.1.9*"

    # Microsoft ASP.NET Core 3.1.9
    $logfile = "$($env:temp)\aspnetcore.log"
    # Write-Verbose "Installing aspnetcore runtime, logging to $logfile"
    # install-qboExecutable -url "https://download.visualstudio.microsoft.com/download/pr/87bcc889-4afa-4c88-839c-d72497b84407/42105fc6c95feb5faa64b2be1b76a830/aspnetcore-runtime-3.1.9-win-x64.exe" -Arguments '/install /norestart /quiet /log "$($logfile)"' -Program "Microsoft ASP.NET Core 3.1.9*"

    # Microsoft ASP.NET Core 5.0.1 Hosting bundle
    $logfile = "$($env:temp)\net50hosting.log"
    Write-Verbose "Installing aspnetcore hosting bundle, logging to $logfile"
    install-qboExecutable -url "https://download.visualstudio.microsoft.com/download/pr/b6271a4b-db02-4245-bf99-974ea96b7ca3/29389344a55c6792bd4e717a254168a2/dotnet-hosting-5.0.1-win.exe" -Arguments '/install /norestart /quiet /log "$($logfile)"' -Program "Microsoft ASP.NET Core 5.0.1*"

    # Microsoft Access database engine 2016
    $logfile = "$($env:temp)\accessdatabaseengine.log"
    Write-Verbose "Installing Access redistribuable, logging to $logfile"
    install-qboExecutable -url "https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe" -Arguments "/log:$($logfile)" -Program "Microsoft Access database engine*"

    # todo: check and install .net48 if required
    # Net48
    # (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full")
    # https://download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/1f81f3962f75eff5d83a60abd3a3ec7b/ndp48-web.exe
}

function Set-SslCert {
    <#
    .SYNOPSIS
        Publishes a Powershell module to a Nuget feed.
        Intended for use after a build, either on a developer's machine or via DevOps release pipeline.
    .INPUTS
        Name: name of the powershell module (must have a matching .psd1 files)
        Path: path to the Output Directory from a build.
        ApiKey: api key to the Nuget repo for publishing
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$SiteName,
        [Parameter(Position=1, Mandatory=$false)]
        [string]$HostHeader,
        [Parameter(Position=2, Mandatory=$false)]
        [string]$Certificate
    )

    if ($HostHeader -eq $null) { $HostHeader = "$($SiteName).localhost.net" }
    New-SelfSignedCertificate -DnsName $HostHeader -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddMonths(60)
}

$Services = @{
    Name           = 'qbo Queue Service'
    # BinaryPathName = Join-Path -Path 'c:\inetpub\wwwroot\' -ChildPath 'bin\QueueService.exe'
    BinaryPathName = 'bin\QueueService.exe'
    DisplayName    = 'qbo Queue Service'
    StartupType    = 'Manual'
    Description    = 'QBO background queue consumer.'
}

function Add-qboServices { 
    <#
    .SYNOPSIS
        Registers Windows services that are part of the qbo installation.
    .INPUTS
        None.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [string]$BasePath = "c:\inetpub\wwwroot\",
        [Parameter(Position=1)]
        [string]$StartType = "Manual"
    )
    foreach ($Service in $Services) {
        if ( Get-Service -Name $Service.Name  -ErrorAction 'silentlycontinue' ) {
            Write-Host  "Service $($Service.Name) exists."
        } else {
            $path = Join-Path -Path $BasePath -ChildPath $Service.BinaryPathName
            Write-Host "Installing Service $($Service.Name) from $path" 
            New-Service -Name $Service.Name -BinaryPathName $path -DisplayName $Service.DisplayName -StartupType $StartType -Description $Service.Description
        }
    }
}

function Stop-qboServices {
    ForEach ($Service in $Services) {
        $ServiceObject = Get-Service -Name $Service.Name  -ErrorAction 'silentlycontinue'
        if ($ServiceObject) {
            Write-Host "Stopping service $($Service.Name)"
            $ServiceObject | Stop-Service
        }
    }    
}

function Start-qboServices {
    ForEach ($Service in $Services) {
        $ServiceObject = Get-Service -Name $Service.Name  -ErrorAction 'silentlycontinue'
        if ($ServiceObject) {
            Write-Host "Starting service $($Service.Name)"
            $ServiceObject | Start-Service
        }
    }    
}

function Remove-qboServices { 
    ForEach ($Service in $Services) {
        $ServiceObject = Get-Service -Name $Service.Name  -ErrorAction 'silentlycontinue'
        if ( $ServiceObject ) {
            Write-Host "Stopping service $($Service.Name)"
            $ServiceObject | Stop-Service

            Write-Host "Removing service $($Service.Name)"
            if ( $PSVersionTable.PSVersion.Major -ge 6) {
                $ServiceObject | Remove-Service
            } else {
                (Get-WmiObject -Class win32_service -Filter ("name='{0}'" -f $Service.Name) ).delete()
            }
            Write-Host "Service $($Service.Name) removed."
        }
        else
        {
            Write-Host "Service $($Service.Name) not installed."
        }
    }
}

function Update-qboDatabase {
    Param(
        [string]$dacpac = "qbo3.Fintech.dacpacs",
        [string]$database,
        [string]$connection
    )

    if ($connection -eq $null) { 
        $connection = read-host "ConnectionString" 
    }
    if ($database -eq $null) { 
        $database = read-host "Database" 
    }
    
    $packagePath = get-qbopackage qbo3.Fintech.dacpacs
    Write-Verbose "Download ${$dacpac} to $($packagePath)"

    Write-Verbose "Installing to $datbase on $connection "
    $dacpacPath = "$($packagepath)\content\Fintech.dacpac"
    publish-qbosqldatabase $database $connection $dacpacPath -verbose
}

function Update-qboWebsite {
    Param(
        [string]$package = "qbo3.Local.WebPackage",
        [string]$website = "Default Web Site",
        [string]$connection
    )

    $webpackagepath = get-qbopackage $package
    $webpackage = "$($webpackagepath)\content\qbo3.Fintech.Local.zip"
    Write-Verbose "Publishing $($webpackage) to $($website)"
    Publish-qboWebsite -PackagePath $webpackage -SiteName $website -Verbose
}

Export-ModuleMember -Function @(
    'Install-qboWebServer', 'Install-qboWebDeploy', 'Install-qboThirdPartyComponents', 
    'Add-qboServices', 'Remove-qboServices', 'Start-qboServices', 'Stop-qboServices', 
    'Update-qboDatabase', 'Update-qboWebsite')