if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb runas
    exit
}

[Console]::ForegroundColor = "Green"

$runtimeDirectory = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
$installUtil = "$($runtimeDirectory)installutil"

$configuredIp = $null
$configuredPort = $null
$configuredSsl = $false
$firstMainMenu = $true
$serviceAccountName = [string] $null
$serviceAccountPassword = [securestring] $null
$serviceWmiQueryFilter = "PathName = '`"$($PSScriptRoot.Replace("\", "\\").Replace("'", "\'"))\\GSS.exe`"'"

function Add-ServiceAccount {
    Set-Variable -Name "serviceAccountName" -Value "GrindstoneSync" -Scope Global
    $differentUserAttempts = 0
    while ((Get-WmiObject -Class Win32_UserAccount -Filter "name = '$serviceAccountName'" | Measure-Object).Count -gt 0) {
        ++$differentUserAttempts > $null
        Set-Variable -Name "serviceAccountName" -Value "GrindstoneSync-$($differentUserAttempts)" -Scope Global
    }
    Set-Variable -Name "serviceAccountPassword" -Value (ConvertTo-SecureString (New-Guid).ToString("n").Substring(0, 16) -AsPlainText -Force) -Scope Global
    New-LocalUser -Name $serviceAccountName -Password $serviceAccountPassword -AccountNeverExpires -PasswordNeverExpires -UserMayNotChangePassword > $null
    Write-Host "`nCreated `"$serviceAccountName`" service account."
}

function Add-SslCertificate {
    Set-Location Cert:\LocalMachine\My > $null
    $thumbprints = Get-ChildItem | Select-Object THUMBPRINT | Where-Object {!($_.psiscontainer)} | ForEach-Object {$_.THUMBPRINT}
    if ($thumbprints) {
        Write-Host "`nHere are the available SSL server certificates:"
        Get-ChildItem | Out-Host
        $thumbprints += "Cancel"
        $thumbprint = Read-Choice "Which is the thumbprint of the SSL server certificate you'd like to add?" $thumbprints
        if ($thumbprint -ne "Cancel") {
            $ipAddress = $configuredIp
            if ($ipAddress -eq "+") {
                $ipAddress = "0.0.0.0"
            }
            Write-Host
            [Console]::ForegroundColor = "White"
            & netsh http add sslcert ipport=$($ipAddress):$($configuredPort) certhash=$thumbprint appid="{1AC5FD6C-FF16-4210-BD2E-729BF50443AC}"
            [Console]::ForegroundColor = "Green"
        }
    } else {
        Write-Host "`nThere are no available SSL server certificates certificates to add."
    }
}

function Add-UrlRegistration {
    Write-Host
    $user = (Get-WmiObject -Class Win32_Service -Filter $serviceWmiQueryFilter).StartName
    if ($user.StartsWith(".\")) {
        $user = "$($env:COMPUTERNAME)\$($user.Substring(2))"
    }
    [Console]::ForegroundColor = "White"
    & netsh http add urlacl url=http://$($configuredIp):$($configuredPort)/ user="`"$user`""
    [Console]::ForegroundColor = "Green"
}

function Get-IsMachineJoinedToDomain {
    return (Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
}

function Install-Service {
    if (-not (Get-IsMachineJoinedToDomain) -and ([string]::IsNullOrWhiteSpace($serviceAccountName) -or -not $serviceAccountPassword)) {
        switch (Read-Choice "I haven't created a service account during this session, so I cannot enter the login and password for you." @("Go back to the Main Menu", "I will manually enter the login and password")) {
            "Go back to the Main Menu" { return }
        }
    }
    :specifyCustomName while ($true) {
        switch (Read-Choice "Do you want to specify a custom name for this Grindstone Sync Server service?" @("No", "Yes")) {
            "Yes" {
                $customName = Read-Host "`nCustom name"
                if ([System.String]::IsNullOrWhiteSpace($customName)) {
                    Write-Host "`nThe custom name cannot be blank."
                    $customName = $null
                    continue specifyCustomName
                } elseif ($customName.Contains("`"")) {
                    Write-Host "`nThe custom name cannot contain a quote character."
                    $customName = $null
                    continue specifyCustomName
                }
            }
        }
        break
    }
    Write-Host
    [Console]::ForegroundColor = "White"
    if ([string]::IsNullOrWhiteSpace($customName)) {
        if ([string]::IsNullOrWhiteSpace($serviceAccountName) -or -not $serviceAccountPassword) {
            & $installUtil "`"$PSScriptRoot\GSS.exe`""
        } else {
            Write-Host "`"$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($serviceAccountPassword)))`""
            & $installUtil /username="`".\$serviceAccountName`"" /password="`"$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($serviceAccountPassword)))`"" "`"$PSScriptRoot\GSS.exe`""
        }
    } elseif ([string]::IsNullOrWhiteSpace($serviceAccountName) -or -not $serviceAccountPassword) {
        & $installUtil /serviceName="`"$customName`"" /displayName="`"$customName`"" "`"$PSScriptRoot\GSS.exe`""
    } else {
        Write-Host "`"$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($serviceAccountPassword)))`""
        & $installUtil /username="`".\$serviceAccountName`"" /password="`"$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($serviceAccountPassword)))`"" /serviceName="`"$customName`"" /displayName="`"$customName`"" "`"$PSScriptRoot\GSS.exe`""
    }
    [Console]::ForegroundColor = "Green"
}

function Invoke-ConsoleTool ([string] $fileName, [string] $arguments) {
    $startInfo = New-Object System.Diagnostics.ProcessStartInfo
    $startInfo.FileName = $fileName
    $startInfo.Arguments = $arguments
    $startInfo.RedirectStandardError = $true
    $startInfo.RedirectStandardOutput = $true
    $startInfo.UseShellExecute = $false
    $process = [System.Diagnostics.Process][System.Diagnostics.Process]::Start($startInfo)
    $streams = @{
        "output" = $process.StandardOutput.ReadToEnd();
        "errors" = $process.StandardError.ReadToEnd()
    }
    $process.WaitForExit()
    $streams
}

function Read-Choice ([string] $question, [array] $answers) {
    while ($true) {
        Write-Host "`n$question`n"
        for ($i = 0; $i -lt $answers.Length; ++$i) {
            Write-Host "$($i + 1): $($answers[$i])"
        }
        $choice = 0
        $chose = [Int32]::TryParse($(Read-Host "`nPlease type the number for your selection and press return"), [ref] $choice)
        if (-not $chose -or $choice -lt 1 -or $choice -gt $answers.Length) {
            Write-Host "`nWhoops, that's not a valid choice! Let's try again..."
            continue;
        }
        return $answers[$choice - 1]
    }
}

function Remove-ServiceAccount {
    if ($serviceAccountName) {
        Remove-LocalUser -Name $serviceAccountName
        Write-Host "`nRemoved `"$serviceAccountName`" service account."
        Set-Variable "serviceAccountName" -Value $null -Scope Global
        Set-Variable "serviceAccountPassword" -Value $null -Scope Global
    }
}

function Remove-SslCertificate {
    $ipAddress = $configuredIp
    if ($ipAddress -eq "+") {
        $ipAddress = "0.0.0.0"
    }
    Write-Host
    [Console]::ForegroundColor = "White"
    & netsh http delete sslcert ipport=$($ipAddress):$($configuredPort)
    [Console]::ForegroundColor = "Green"
}

function Remove-UrlRegistration {
    Write-Host
    [Console]::ForegroundColor = "White"
    & netsh http delete urlacl url=http://$($configuredIp):$($configuredPort)/
    [Console]::ForegroundColor = "Green"
}

function Uninstall-Service {
    Write-Host
    $serviceWmiRecord = Get-WmiObject -Class Win32_Service -Filter $serviceWmiQueryFilter
    if (($serviceWmiRecord | Measure-Object).Count -ge 1) {
        Set-Variable "serviceAccountName" -Value $serviceWmiRecord.StartName -Scope Global
        if ($serviceAccountName.StartsWith(".\")) {
            Set-Variable "serviceAccountName" -Value $serviceWmiRecord.StartName.Substring(2) -Scope Global
        }
        [Console]::ForegroundColor = "White"
        & $installUtil /serviceName="`"$($serviceWmiRecord.Name)`"" /displayName="`"$($serviceWmiRecord.DisplayName)`"" "`"$PSScriptRoot\GSS.exe`"" -u
        [Console]::ForegroundColor = "Green"
    } else {
        Write-Host "Service is already uninstalled."
    }
}

$createServiceAccount = "Create a service account for Grindstone Sync Server"
$installService = "Install Grindstone Sync Server as a service"
$registerUrl = "Register Grindstone Sync Server's URL with Windows"
$addSslCertificate = "Add an SSL server certificate"
$removeSslCertificate = "Remove SSL server certificate"
$unregisterUrl = "Unregister Grindstone Sync Server's URL with Windows"
$uninstallService = "Uninstall Grindstone Sync Server as a service"
$removeServiceAccount = "Remove the service account for Grindstone Sync Server"
$reexamineEnvironment = "Re-examine the environment"
$exit = "I don't need anything else"

Write-Host "GRINDSTONE SYNC SERVER INSTALLATION ASSISTANT"
while ($true) {
    $mainMenuChoices = @()
    Write-Host "`nPlease give me just a moment to examine this environment...`n"
    $machineIsJoinedToDomain = Get-IsMachineJoinedToDomain
    if ($machineIsJoinedToDomain) {
        Write-Host "This machine is joined to a domain, so you will need to manage the creation/removal of the service account yourself."
    } else {
        Write-Host "This machine is not joined to a domain, so you I can help you create or remove the service account."
    }
    $serviceWmiRecord = Get-WmiObject -Class Win32_Service -Filter $serviceWmiQueryFilter
    $serviceIsInstalled = ($serviceWmiRecord | Measure-Object).Count -ge 1
    $serviceConfigIsAvailable = Test-Path "$PSScriptRoot\Quantum.Server.config"
    if ($serviceConfigIsAvailable) {
        [xml] $serviceConfig = Get-Content "$PSScriptRoot\Quantum.Server.config"
        Set-Variable -Name "configuredIp" -Value $serviceConfig.DocumentElement.GetAttribute("ipAddress") -Scope Global
        if (-not $configuredIp) {
            Set-Variable -Name "configuredIp" -Value "+" -Scope Global
        }
        Set-Variable -Name "configuredPort" -Value $serviceConfig.DocumentElement.GetAttribute("port") -Scope Global
        if (-not $configuredPort) {
            Set-Variable -Name "configuredPort" -Value "6457" -Scope Global
        }
        $useSslStr = $serviceConfig.DocumentElement.GetAttribute("useSsl")
        if ($useSslStr) {
            Set-Variable -Name "configuredSsl" -Value ([System.Convert]::ToBoolean($useSslStr)) -Scope Global
        } else {
            Set-Variable -Name "configuredSsl" -Value $false -Scope Global
        }
        if ($configuredIp -eq "+") {
            Write-Host "The Web Services of Grindstone Sync Server are configured to listen on port $configuredPort of any local IP address."
        } else {
            Write-Host "The Web Services of Grindstone Sync Server are configured to listen on port $configuredPort of `"$configuredIp`"."
        }
        $sslIpAddress = $configuredIp
        if ($sslIpAddress -eq "+") {
            $sslIpAddress = "0.0.0.0"
        }    
        $sslAdded = (Invoke-ConsoleTool "netsh" "http show sslcert ipport=$($sslIpAddress):$($configuredPort)").output -match [System.Text.RegularExpressions.Regex]::Escape("$($sslIpAddress):$($configuredPort)")
        if ($configuredSsl) {
            Write-Host "Grindstone Sync Server is expecting incoming connections to be secured (HTTPS)."
            if (-not $sslAdded) {
                Write-Warning "An SSL server certificate has not been added, which is required when using HTTPS."
            }
        } else {
            Write-Host "Grindstone Sync Server is expecting incoming connections not to be secured (HTTP)."
            if ($sslAdded) {
                Write-Warning "An SSL server certificate has been added, which is not permitted when using HTTP."
            }
        }
        if ($serviceIsInstalled) {
            $netshUrlReservations = Invoke-ConsoleTool "netsh" "http show urlacl url=http://$($configuredIp):$($configuredPort)/"
            if ($netshUrlReservations.output -match "Reserved URL") {
                $user = (Get-WmiObject -Class Win32_Service -Filter $serviceWmiQueryFilter).StartName
                if ($user.StartsWith(".\")) {
                    $user = "$($env:COMPUTERNAME)\$($user.Substring(2))"
                }
                if (-not ($netshUrlReservations.output -match "$([System.Text.RegularExpressions.Regex]::Escape($user))")) {
                    Write-Warning "The URL has been registered with Windows, but not for the service's account."
                } else {
                    Write-Host "The URL has been registered with Windows."
                }
            } else {
                Write-Warning "The URL has not been registered with Windows, which is required for Web Services to work."
            }
        }
    } else {
        Set-Variable -Name "configuredIp" -Value $null -Scope Global
        Set-Variable -Name "configuredPort" -Value $null -Scope Global
        Set-Variable -Name "configuredSsl" -Value $false -Scope Global
        Write-Warning "The configuration of Web Services has not been initialized."
    }
    Write-Host "If you want to initialize or change the configuration of Web Services, please launch GSS.exe on the desktop,"
    Write-Host "  make any changes you like on the Configuration tab, and then click Save Configuration."
    if ($serviceIsInstalled) {
        Write-Host "The service is installed as `"$($serviceWmiRecord.DisplayName)`", using the account `"$($serviceWmiRecord.StartName)`"."
        if ($serviceConfigIsAvailable) {
            $mainMenuChoices += $registerUrl
            $mainMenuChoices += $addSslCertificate
        }
    } else {
        Write-Host "This Grindstone Sync Server does not appear to be installed as a service at the moment."
        if (-not $machineIsJoinedToDomain) {
            if (-not $serviceAccountName) {
                $mainMenuChoices += $createServiceAccount
            } else {
                $mainMenuChoices += $removeServiceAccount
            }
        }
        $mainMenuChoices += $installService
    }
    if ($serviceIsInstalled) {
        if ($serviceConfigIsAvailable) {
            $mainMenuChoices += $removeSslCertificate
            $mainMenuChoices += $unregisterUrl
        }
        $mainMenuChoices += $uninstallService
    }
    $mainMenuChoices += $reexamineEnvironment
    $mainMenuChoices += $exit
    switch (Read-Choice $(if ($firstMainMenu) {"How may I be of assistance?"} else {"May I help with anything else?"}) $mainMenuChoices) {
        $addSslCertificate { Add-SslCertificate }
        $createServiceAccount { Add-ServiceAccount }
        $exit { [Console]::ForegroundColor = "Gray"; exit }
        $installService { Install-Service }
        $registerUrl { Add-UrlRegistration }
        $removeServiceAccount { Remove-ServiceAccount }
        $removeSslCertificate { Remove-SslCertificate }
        $uninstallService { Uninstall-Service }
        $unregisterUrl { Remove-UrlRegistration }
    }
    $firstMainMenu = $false
}