Public/ps1/Configuration/Service/Install-ApprxrService.ps1

<#
.SYNOPSIS
    Installs Apprxr as a Windows service using NSSM (Non-Sucking Service Manager).
 
.DESCRIPTION
    Downloads and extracts NSSM to the specified installation folder (or the Apprxr configuration folder if not specified) if not already present. Configures a Windows service to run Apprxr as a background service using PowerShell and NSSM. The service name and entrypoint can be extended with a custom string using the NameExtension parameter. If NameExtension is provided, it is appended to the service name and Start-Apprxr command, and saved to configuration for consistent service management by other Apprxr service commands.
     
    If InstallationFolder is provided, NSSM and the service will be installed in that folder. Otherwise, the Apprxr configuration folder is used by default.
 
    The NssmArch parameter allows you to specify which NSSM executable to use: 'Win64' (default), 'Win32', or 'Generic'.
 
    The RunAsUser parameter allows you to specify a user account under which the service will run. If provided, you will be prompted for the password, and the service will be configured to run as that user.
 
.PARAMETER NameExtension
    Optional. A string to append to the service name and Start-Apprxr entrypoint. If provided, the service will be named 'ApprxrService<NameExtension>' and will run 'Start-Apprxr<NameExtension>'. This value is also saved to configuration for use by other service management commands.
 
.PARAMETER InstallationFolder
    Optional. The folder where NSSM and the service should be installed. If not specified, the Apprxr configuration folder is used.
 
.PARAMETER NssmArch
    Optional. Specifies which NSSM executable to use. Valid values are 'Win64' (default), 'Win32', or 'Generic'.
    - 'Win64': Use the win64 version of NSSM (default).
    - 'Win32': Use the win32 version of NSSM.
    - 'Generic': Use the first nssm.exe found in the extracted folder.
 
.PARAMETER RunAsUser
    Optional. The username (in DOMAIN\User or .\User format) to run the service as. If specified, you will be prompted for the password, and the service will be configured to run as this user.
 
.EXAMPLE
    Install-ApprxrService
    # Installs the service as 'ApprxrService' running 'Start-Apprxr' from the Apprxr module, using the default configuration folder.
 
.EXAMPLE
    Install-ApprxrService -NameExtension 'Test'
    # Installs the service as 'ApprxrServiceTest' running 'Start-ApprxrTest', and saves 'Test' as the ServiceNameExtension in configuration.
 
.EXAMPLE
    Install-ApprxrService -NameExtension 'Prod'
    # Installs the service as 'ApprxrServiceProd' running 'Start-ApprxrProd'.
 
.EXAMPLE
    Install-ApprxrService -InstallationFolder 'D:\Services\Apprxr'
    # Installs the service and NSSM in the specified folder instead of the default configuration folder.
 
.EXAMPLE
    Install-ApprxrService -NameExtension 'QA' -InstallationFolder 'D:\Services\ApprxrQA'
    # Installs the service as 'ApprxrServiceQA' running 'Start-ApprxrQA', and installs NSSM in the specified folder.
 
.EXAMPLE
    Install-ApprxrService -NssmArch Win32
    # Installs the service using the win32 version of NSSM.
 
.EXAMPLE
    Install-ApprxrService -NssmArch Generic
    # Installs the service using the first nssm.exe found in the extracted folder (generic selection).
 
.EXAMPLE
    Install-ApprxrService -RunAsUser 'DOMAIN\\ServiceUser'
    # Installs the service to run as the specified user. You will be prompted for the password.
 
.EXAMPLE
    Install-ApprxrService -NameExtension 'Prod' -RunAsUser '.\\LocalUser'
    # Installs the service as 'ApprxrServiceProd' running 'Start-ApprxrProd', and configures it to run as the specified local user.
 
.NOTES
    - Requires administrative privileges to install a Windows service.
    - NSSM is downloaded from https://nssm.cc/release/nssm-2.24.zip if not present.
    - The service will run PowerShell with the Apprxr module imported and execute the Start-Apprxr* command.
    - The NameExtension value is saved to configuration for use by Start/Stop/Remove/Restart-ApprxrService commands.
    - If NSSM extraction fails, the script will throw an error.
    - If InstallationFolder is not specified, the Apprxr configuration folder is used by default.
    - The NssmArch parameter controls which NSSM executable is used for the service.
    - The RunAsUser parameter allows running the service as a specific user account.
#>



function Install-ApprxrService {

    [CmdletBinding()]
    param(
        [string]$NameExtension,
        [string]$InstallationFolder,
        [ValidateSet('Win64','Win32','Generic')]
        [string]$NssmArch = 'Win64',
        [string]$RunAsUser
    )

    # Check for administrative privileges
    $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        throw 'Install-ApprxrService must be run as Administrator.'
    }

    $targetFolder = if ($InstallationFolder) { $InstallationFolder } else { Get-ApprxrConfigurationFolder }
    if (-not (Test-Path $targetFolder)) {
        New-Item -Path $targetFolder -ItemType Directory -Force | Out-Null
    }


    # Validate if nssm.exe is downloaded already
    $nssmFound = Get-ChildItem -Path $targetFolder -Recurse -Filter nssm.exe | Select-Object -First 1
   
    if (-not $nssmFound) {
        Write-Host "Downloading NSSM..." -ForegroundColor Yellow
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $nssmUrl = 'https://apprx.nl/support/tools/nssm-2.24.zip'
        $zipPath = Join-Path $targetFolder 'nssm.zip'
        Invoke-WebRequest -Uri $nssmUrl -OutFile $zipPath
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $targetFolder)
        Remove-Item $zipPath
    }

    
    $archFolder = switch ($NssmArch) {
        'Win64' { 'win64' }
        'Win32' { 'win32' }
        default { '' }
    }
    write-host "Using NSSM architecture: $NssmArch" -ForegroundColor Green
    if ($archFolder) {
        $nssmVersionFolder = Get-ChildItem -Path $targetFolder -Directory | Where-Object { $_.Name -like 'nssm*' } | Select-Object -First 1
        write-host "NSSM version folder found at $($nssmVersionFolder.FullName)" -ForegroundColor Green
        if ($nssmVersionFolder) {
            $nssmExe = Get-ChildItem -Path (Join-Path $nssmVersionFolder.FullName $archFolder) -Recurse -Filter nssm.exe | Select-Object -First 1
            write-host "NSSM found at $($nssmExe.FullName)" -ForegroundColor Green
        } else {
            throw 'Could not find NSSM version folder in target folder.'
        }
    } else {
        $nssmExe = Get-ChildItem -Path $targetFolder -Recurse -Filter nssm.exe | Select-Object -First 1
    }
    
    # Use pwsh if available, otherwise fallback to powershell.exe
    $pwshCmd = Get-Command pwsh -ErrorAction SilentlyContinue
    if ($pwshCmd) {
        $pwshPath = $pwshCmd.Source
    } else {
        $pwshCmd = Get-Command powershell -ErrorAction SilentlyContinue
        if ($pwshCmd) {
            $pwshPath = $pwshCmd.Source
        } else {
            throw 'Neither pwsh nor powershell.exe was found on this system.'
        }
    }
    $modulePath = Join-Path $PSScriptRoot '..'
    $modulePath = Join-Path $modulePath '..'
    $modulePath = Join-Path $modulePath '..'
    $modulePath = Join-Path $modulePath 'Apprxr.psm1'
    $modulePath = [System.IO.Path]::GetFullPath($modulePath)
    $serviceSuffix = if ($NameExtension) { "$NameExtension" } else { '' }
    $ServiceName = "ApprxrService$serviceSuffix"
    $startCommand = "Start-Apprxr"
    $arguments = "start-apprxr; start-sleep 86400"
    & ($nssmExe.FullName) install $ServiceName $pwshPath $arguments

    if ($RunAsUser) {
        $password = Read-Host -Prompt "Enter password for user $RunAsUser" -AsSecureString
        $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
        $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
        & ($nssmExe.FullName) set $ServiceName ObjectName $RunAsUser $plainPassword
        [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
        Write-Host "Configured service to run as $RunAsUser" -ForegroundColor Green
    }

    if ($NameExtension) {
        Set-ApprxrConfigurationValue -Name 'ServiceNameExtension' -Value $NameExtension
    }
    Log-Apprxr "Service '$ServiceName' installed to run $startCommand from module using NSSM ($NssmArch) in folder '$targetFolder'."
}