Modules/WindowsService.ps1

<#
    Stops the given Windows Service and waits for 5 seconds.
#>

function Stop-WindowsService
{
    param(
        [Parameter(Mandatory=$true, Position=1)]
        [string]$Name  = $null,
        
        [Parameter(Mandatory=$false, Position=2)]
        [string]$Sleep = 5 # seconds
    )
    
    Begin {
    }
    
    Process {
        # Verify if the service exists, and if yes stop it and wait for new state.
        if(Assert-ServiceExists $Name) {            
            $Service = Get-Service $Name | Where-Object {$_.status -eq 'Running'}

            if ($Service) {
                Write-Log "Stop Service: $Name`n"
                $Service | Stop-Service -Pass
            } 
            else {
                Write-Log "Service $Name not Running`n"
            }

            Start-Sleep -s $Sleep
        }
    }
    
    End {
    }
}

<#
    Removes the given Windows Service.
#>

function Remove-WindowsService
{
    param(
        [Parameter(Mandatory=$true, Position=1)]
        [string]$ServiceName = $null
    )
    
    Begin {
    }
    
    Process {
        # Verify if the service already exists, and if yes remove it
        if(Assert-ServiceExists $ServiceName) {
            if(-Not (Assert-ServiceStopped)) {
                Stop-WindowsService -Name $ServiceName
            }          
            
            # Remove Service
            Start-Process -FilePath sc.exe -Args "delete $ServiceName" -Verb runAs -Wait
            
            Write-Log "Service removed: $ServiceName"
        }
    }
    
    End {
    }
}

<#
    Creates a Windows Service and sets the configuration.
#>

function New-WindowsService
{
    param(
        [Parameter(Mandatory=$true, Position=1)]
        [string]$ServiceName = $null,

        [Parameter(Mandatory=$true, Position=2)]
        [string]$DisplayName = $null,

        [Parameter(Mandatory=$true, Position=3)]
        [string]$BinaryPath  = $null,

        [Parameter(Mandatory=$false, Position=4)]
        [string]$Description = $null,

        [Parameter(Mandatory=$false, Position=5)]
        [string]$StartUpType = $null, # Automatic, Manual, Disabled

        [Parameter(Mandatory=$false, Position=6)]
        [string]$DelayedStart= $null, # DelayedAutoStart

        [Parameter(Mandatory=$false, Position=7)]
        [string]$User       = $null, # NT AUTHORITY\LocalSystem, NT AUTHORITY\LocalService, NT AUTHORITY\NetworkService, <Domain\User>

        [Parameter(Mandatory=$false, Position=8)]
        [string]$Password    = $null
    )
    
    Begin {
        if ((Test-Path $BinaryPath) -eq $false)
        {
            Write-Log "Service binary path not found: $BinaryPath. Service was NOT installed." -LogLevel Error
        }
    }
    
    Process {
        Write-Log "Installing service: $serviceName`n"
        
        # Install dotNET Service.
        
        New-Service -BinaryPathName $BinaryPath -Name $ServiceName -DisplayName $DisplayName
                    
        if($Description) {
            Set-Service $ServiceName -Description $Description
        }
        
        if($StartUpType) {
            Set-Service $ServiceName -StartupType $StartupType
            
            if($StartUpType -eq "Automatic" -and $DelayedStart) {
                Start-Process -FilePath sc.exe -Args "config $ServiceName start=delayed-auto" -Verb runAs -Wait
            }
        }
        
        if($User) {
            # if password is empty, create a dummy one to allow having credentials for system accounts:
            # NT AUTHORITY\LocalSystem
            # NT AUTHORITY\LocalService
            # NT AUTHORITY\NetworkService

            if ([string]::IsNullOrEmpty($Password)) {
                $Password = "dummy"
            }
            else {
                # Add account to logon as a service.
                Add-AccountToLogonAsService $User
            }
            
            $Service = Get-WmiObject -Class Win32_Service -Filter "name='$ServiceName'"
            
            Stop-WindowsService -Name $ServiceName
            
            $Service.change($null, $null, $null, $null, $null, $null, $User, $Password, $null, $null, $null)
        }
        
        Write-Log "Installation completed: $ServiceName"
    }
    
    End {
    }
}

<#
    Adds a Windows Account to LogonAsService
#>

function Add-AccountToLogonAsService {
    param(
        [Parameter(Mandatory=$true, Position=1)]
        [string]$Username = $null
    )
    
    Begin {
    }
    
    Process {
        $sidstr = $null
        
        try {
            # in case somone is using a local account like .\FooBar the .\ will not work and will get replaced
            $Username = $Username -replace "\.\\", ""
            $ntprincipal = new-object System.Security.Principal.NTAccount "$Username"
            $sid = $ntprincipal.Translate([System.Security.Principal.SecurityIdentifier])
            $sidstr = $sid.Value.ToString()
        } catch {
            $sidstr = $null
        }
        
        Write-Log "Account: $($Username)"
        
        if([string]::IsNullOrEmpty($sidstr)) {
            Write-Log "Account $Username not found!" -LogLevel "Error"
            exit -1
        }
        
        Write-Log "Account SID: $($sidstr)"        
        
        $tmp = [System.IO.Path]::GetTempFileName()
        
        Write-Log "Export current Local Security Policy"
        
        secedit.exe /export /cfg "$($tmp)"
        
        $c = Get-Content -Path $tmp
        
        $currentSetting = ""
        
        foreach($s in $c) {
            if( $s -like "SeServiceLogonRight*") {
                $x = $s.split("=",[System.StringSplitOptions]::RemoveEmptyEntries)
                $currentSetting = $x[1].Trim()
            }
        }
        
        if( $currentSetting -notlike "*$($sidstr)*" ) {
            Write-Log "Modify Setting ""Logon as a Service"""
            
            if( [string]::IsNullOrEmpty($currentSetting) ) {
                $currentSetting = "*$($sidstr)"
            } else {
                $currentSetting = "*$($sidstr), $($currentSetting)"
            }
            
            Write-Log "$currentSetting"
            
            $outfile = @"
[Unicode]
Unicode=yes
[Version]
signature="`$CHICAGO`$"
Revision=1
[Privilege Rights]
SeServiceLogonRight = $($currentSetting)
"@

        
            $tmp2 = [System.IO.Path]::GetTempFileName()
            
            
            Write-Log "Import new settings to Local Security Policy"
            $outfile | Set-Content -Path $tmp2 -Encoding Unicode -Force
            
            #notepad.exe $tmp2
            Push-Location (Split-Path $tmp2)
            
            try {
                secedit.exe /configure /db "secedit.sdb" /cfg "$($tmp2)" /areas USER_RIGHTS 
            } finally {
                Pop-Location
            }
        } else {
            Write-Log "NO ACTIONS REQUIRED! Account already in ""Logon as a Service"""
        }
        
        Write-Log "Done."
    }
    
    end {}
}

<#
    Determines if a Service exists with a name as defined in $serviceName.
    Returns a boolean $True or $false.
#>
 
function Assert-ServiceExists {
    param(
        [Parameter(Mandatory=$true, Position=1)]
        [string] $Name
    )

    # If you use just "Get-Service $Name", it will return an error if
    # the service didn't exist. Trick Get-Service to return an array of
    # Services, but only if the name exactly matches the $Name.
    # This way you can test if the array is empty.
    if (Get-Service "$Name*" -Include $Name) {
        return $true
    }
    Write-Log "The Service $Name does not Exist" -LogLevel "Error"
    
    return $false
}

<#
    Starts the given Windows Service and waits for the service to reach the Stopped state or a maximum of 2 minutes seconds.
#>
 
function Start-WindowsService()
{
    Get-Service -Name $serviceName | Set-Service -Status Running
    Assert-ServicesStarted
}

function Assert-ServicesStarted () 
{
    Start-Sleep -s 5
    
    $SmokeTestService = Get-Service -Name $serviceName
    
    if ($SmokeTestService.Status -ne "Running") {
        Throw "Smoke test: FAILED. (SERVICE FAILED TO START)"
    } 
    else {
        return $true
    }
}

function Assert-ServiceStopped()
{
    Start-Sleep -s 5
    
    $SmokeTestService = Get-Service -Name $serviceName
    
    if ($SmokeTestService.Status -ne "Stopped") {
        return $false
    } 
    else {
        return $true  
    }
}