HASS.io on Windows (#18)
* First draft of PowerShell Installer. * Refined approach to install and service Used a powershell script for service creation that I have used in the past to make it simpler and will be modifying it to be the control process as well. * Path cleanup and ready for inital review.
This commit is contained in:
parent
9b13548a86
commit
da899e64fd
2 changed files with 1104 additions and 0 deletions
107
install/hassio_install.ps1
Normal file
107
install/hassio_install.ps1
Normal file
|
@ -0,0 +1,107 @@
|
|||
#requires -version 4.0
|
||||
#requires -runasadministrator
|
||||
|
||||
param (
|
||||
[string]
|
||||
$DataSharePath = "/usr/share/hassio",
|
||||
|
||||
[string]
|
||||
$BinaryLocation = "$($env:ALLUSERSPROFILE)\hassio",
|
||||
|
||||
[string]
|
||||
$HassIoSourceUrlRoot = "https://raw.githubusercontent.com/home-assistant/hassio-build",
|
||||
|
||||
[string]
|
||||
$HassIoSourceBranch = "master",
|
||||
|
||||
[Switch]
|
||||
$Torch
|
||||
)
|
||||
|
||||
# Setup Variables
|
||||
$URL_VERSION="https://raw.githubusercontent.com/home-assistant/hassio/master/version.json"
|
||||
$URL_HASSIO_SERVICE="$HassIoSourceUrlRoot/$HassIoSourceBranch/install/service/hassio_supervisor.ps1"
|
||||
|
||||
$DOCKER_REPO="homeassistant"
|
||||
$HOMEASSISTANT_DOCKER="$DOCKER_REPO/qemux86-64-homeassistant"
|
||||
$HASSIO_DOCKER="$DOCKER_REPO/amd64-hassio-supervisor"
|
||||
|
||||
|
||||
|
||||
Write-Host "`n------------------------------------------" -ForegroundColor White
|
||||
Write-Host "- HASS.IO Installer for Windows -" -ForegroundColor White
|
||||
Write-Host "- This will install the hass.io system -" -ForegroundColor White
|
||||
Write-Host "- on docker in a windows environemnt. -" -ForegroundColor White
|
||||
Write-Host "------------------------------------------" -ForegroundColor White
|
||||
|
||||
# Read infos from web
|
||||
$HASSIO_VERSION= (ConvertFrom-Json (Invoke-WebRequest -Uri $URL_VERSION).Content).hassio
|
||||
$GENERIC_HC_VERSION= (ConvertFrom-Json (Invoke-WebRequest -Uri $URL_VERSION).Content).generic
|
||||
|
||||
Write-Host "`n[INFO] Version Information"
|
||||
Write-Host "HASSIO_VERSION = $HASSIO_VERSION"
|
||||
Write-Host "GENERIC_HC_VERSION = $GENERIC_HC_VERSION"
|
||||
|
||||
|
||||
if($Torch) {
|
||||
Write-Host "Warning. This will torch all areas before installation starts. You WILL lose data..."
|
||||
Write-Host " -- The Docker containers hassio_supervisor and homeassistant will be completly deleted. -- "
|
||||
Read-Host -Prompt "LAST CHANCE. Press CTRL-C to about this now!!!"
|
||||
|
||||
if((Test-Path -Path $BinaryLocation)) {
|
||||
& $BinaryLocation\hassio_supervisor.ps1 -Remove
|
||||
Remove-Item -Path $BinaryLocation -Recurse -Force
|
||||
}
|
||||
docker rm hassio_supervisor -f
|
||||
docker rmi "$($HASSIO_DOCKER)"
|
||||
docker rm homeassistant -f
|
||||
docker rmi "$($HOMEASSISTANT_DOCKER)"
|
||||
}
|
||||
|
||||
if(!(Test-Path -Path $BinaryLocation)) {
|
||||
Write-Host "[INFO] Creating the binary location @ $BinaryLocation"
|
||||
New-Item -Path $BinaryLocation -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
$hassIoConfiguration = Join-Path $BinaryLocation 'configuration.json'
|
||||
|
||||
##
|
||||
# Install supervisor
|
||||
Write-Host "[INFO] Install supervisor docker"
|
||||
docker pull "$($HASSIO_DOCKER):$($HASSIO_VERSION)"
|
||||
docker tag "$($HASSIO_DOCKER):$($HASSIO_VERSION)" "$($HASSIO_DOCKER):latest"
|
||||
|
||||
##
|
||||
# Download service script
|
||||
Write-Host "[INFO] Downloading service script to $BinaryLocation\hassio_supervisor.ps1"
|
||||
(Invoke-WebRequest -Uri "$URL_HASSIO_SERVICE").Content | Set-Content -Path "$BinaryLocation\hassio_supervisor.ps1" -Encoding UTF8
|
||||
|
||||
##
|
||||
# Generate runtime configuration
|
||||
Write-Host "[INFO] Generating configuration: $hassIoConfiguration"
|
||||
$defaultConfiguration = "{
|
||||
`"datapath`":`"$DataSharePath`",
|
||||
`"home_assistant_docker`":`"$HOMEASSISTANT_DOCKER`",
|
||||
`"hassio_supervisor_docker`":`"$HASSIO_DOCKER`"
|
||||
}"
|
||||
Write-Host "[INFO] ... $defaultConfiguration "
|
||||
$defaultConfiguration | Set-Content -Path $hassIoConfiguration -Encoding UTF8
|
||||
|
||||
##
|
||||
# Installing Service
|
||||
Write-Host "[INFO] Installing service."
|
||||
$status = & $BinaryLocation\hassio_supervisor.ps1 -status
|
||||
Write-Host "[INFO] Service Current Status: $status"
|
||||
if($status -ne "Not Installed") {
|
||||
Write-Host "[INFO] Removing existing service."
|
||||
& $BinaryLocation\hassio_supervisor.ps1 -Remove
|
||||
}
|
||||
|
||||
& $BinaryLocation\hassio_supervisor.ps1 -Setup
|
||||
|
||||
|
||||
##
|
||||
# Starting Service
|
||||
Write-Host "[INFO] Starting hass.io Service"
|
||||
& $BinaryLocation\hassio_supervisor.ps1 -Start
|
||||
|
997
install/service/hassio_supervisor.ps1
Normal file
997
install/service/hassio_supervisor.ps1
Normal file
|
@ -0,0 +1,997 @@
|
|||
#Requires -version 2
|
||||
# Orginal code and information from
|
||||
# https://msdn.microsoft.com/en-us/magazine/mt703436.aspx
|
||||
<#
|
||||
.SYNOPSIS
|
||||
hass.io supervisor service implimented in Powershell. This wraps the docker processes.
|
||||
|
||||
.DESCRIPTION
|
||||
This script manages the supervisor process for hass.io
|
||||
It dynamically generates a small hassio_supervisor.exe wrapper, that in turn
|
||||
invokes this PowerShell script again for its start and stop events.
|
||||
|
||||
.PARAMETER Start
|
||||
Start the service.
|
||||
|
||||
.PARAMETER Stop
|
||||
Stop the service.
|
||||
|
||||
.PARAMETER Restart
|
||||
Stop then restart the service.
|
||||
|
||||
.PARAMETER Status
|
||||
Get the current service status: Not installed / Stopped / Running
|
||||
|
||||
.PARAMETER Setup
|
||||
Install the service.
|
||||
|
||||
.PARAMETER Remove
|
||||
Uninstall the service.
|
||||
|
||||
.PARAMETER Service
|
||||
Run the service in the background. Used internally by the script.
|
||||
Do not use, except for test purposes.
|
||||
|
||||
.PARAMETER Control
|
||||
Send a control message to the service thread.
|
||||
|
||||
.PARAMETER Version
|
||||
Display this script version and exit.
|
||||
|
||||
.EXAMPLE
|
||||
# Setup the service and run it for the first time
|
||||
C:\PS>.\hassio_supervisor.ps1 -Status
|
||||
Not installed
|
||||
C:\PS>.\hassio_supervisor.ps1 -Setup
|
||||
C:\PS># At this stage, a copy of hassio_supervisor.ps1 is present in the path
|
||||
C:\PS>hassio_supervisor -Status
|
||||
Stopped
|
||||
C:\PS>hassio_supervisor -Start
|
||||
C:\PS>hassio_supervisor -Status
|
||||
Running
|
||||
C:\PS># Load the log file in Notepad.exe for review
|
||||
C:\PS>notepad ${ENV:windir}\Logs\hassio_supervisor.log
|
||||
|
||||
.EXAMPLE
|
||||
# Stop the service and uninstall it.
|
||||
C:\PS>hassio_supervisor -Stop
|
||||
C:\PS>hassio_supervisor -Status
|
||||
Stopped
|
||||
C:\PS>hassio_supervisor -Remove
|
||||
C:\PS># At this stage, no copy of hassio_supervisor.ps1 is present in the path anymore
|
||||
C:\PS>.\hassio_supervisor.ps1 -Status
|
||||
Not installed
|
||||
|
||||
.EXAMPLE
|
||||
# Send a control message to the service, and verify that it received it.
|
||||
C:\PS>hassio_supervisor -Control Hello
|
||||
C:\PS>Notepad C:\Windows\Logs\hassio_supervisor.log
|
||||
# The last lines should contain a trace of the reception of this Hello message
|
||||
#>
|
||||
|
||||
[CmdletBinding(DefaultParameterSetName='Status')]
|
||||
Param(
|
||||
[Parameter(ParameterSetName='Start', Mandatory=$true)]
|
||||
[Switch]$Start, # Start the service
|
||||
|
||||
[Parameter(ParameterSetName='Stop', Mandatory=$true)]
|
||||
[Switch]$Stop, # Stop the service
|
||||
|
||||
[Parameter(ParameterSetName='Restart', Mandatory=$true)]
|
||||
[Switch]$Restart, # Restart the service
|
||||
|
||||
[Parameter(ParameterSetName='Status', Mandatory=$false)]
|
||||
[Switch]$Status = $($PSCmdlet.ParameterSetName -eq 'Status'), # Get the current service status
|
||||
|
||||
[Parameter(ParameterSetName='Setup', Mandatory=$true)]
|
||||
[Switch]$Setup, # Install the service
|
||||
|
||||
[Parameter(ParameterSetName='Remove', Mandatory=$true)]
|
||||
[Switch]$Remove, # Uninstall the service
|
||||
|
||||
[Parameter(ParameterSetName='Service', Mandatory=$true)]
|
||||
[Switch]$Service, # Run the service
|
||||
|
||||
[Parameter(ParameterSetName='Control', Mandatory=$true)]
|
||||
[String]$Control = $null, # Control message to send to the service
|
||||
|
||||
[Parameter(ParameterSetName='Version', Mandatory=$true)]
|
||||
[Switch]$Version, # Get this script version
|
||||
|
||||
[Parameter(ParameterSetName='ViewLog', Mandatory=$true)]
|
||||
[Switch]
|
||||
$ViewLog,
|
||||
|
||||
[Parameter(ParameterSetName='Attach', Mandatory=$true)]
|
||||
[Switch]
|
||||
$Attach
|
||||
)
|
||||
|
||||
$scriptVersion = "2017-06-06"
|
||||
|
||||
# This script name, with various levels of details
|
||||
$argv0 = Get-Item $MyInvocation.MyCommand.Definition
|
||||
$script = $argv0.basename # Ex: hassio_supervisor
|
||||
$scriptName = $argv0.name # Ex: hassio_supervisor.ps1
|
||||
$scriptFullName = $argv0.fullname # Ex: C:\Temp\hassio_supervisor.ps1
|
||||
|
||||
# Global settings
|
||||
$serviceName = $script # A one-word name used for net start commands
|
||||
$serviceDisplayName = "Hass.io the ultimate Home Automation hub"
|
||||
$ServiceDescription = "Hass.io turns your computer into the ultimate Home Automation hub. It takes away all the hassle of installing Home Assistant and related applications and keeping them up to date. Updates to the operating system, Home Assistant, and any add-ons can be done via the Home Assistant UI with a single click."
|
||||
$pipeName = "Service_$serviceName" # Named pipe name. Used for sending messages to the service task
|
||||
# $installDir = "${ENV:ProgramFiles}\$serviceName" # Where to install the service files
|
||||
$installDir = "${ENV:windir}\System32" # Where to install the service files
|
||||
$scriptCopy = "$installDir\$scriptName"
|
||||
$exeName = "$serviceName.exe"
|
||||
$exeFullName = "$installDir\$exeName"
|
||||
$logDir = "${ENV:windir}\Logs" # Where to log the service messages
|
||||
$logFile = "$logDir\$serviceName.log"
|
||||
$logName = "Application" # Event Log name (Unrelated to the logFile!)
|
||||
# Note: The current implementation only supports "classic" (ie. XP-compatble) event logs.
|
||||
# To support new style (Vista and later) "Applications and Services Logs" folder trees, it would
|
||||
# be necessary to use the new *WinEvent commands instead of the XP-compatible *EventLog commands.
|
||||
# Gotcha: If you change $logName to "NEWLOGNAME", make sure that the registry key below does not exist:
|
||||
# HKLM\System\CurrentControlSet\services\eventlog\Application\NEWLOGNAME
|
||||
# Else, New-EventLog will fail, saying the log NEWLOGNAME is already registered as a source,
|
||||
# even though "Get-WinEvent -ListLog NEWLOGNAME" says this log does not exist!
|
||||
|
||||
# If the -Version switch is specified, display the script version and exit.
|
||||
if ($Version) {
|
||||
Write-Output $scriptVersion
|
||||
return
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Now #
|
||||
# #
|
||||
# Description Get a string with the current time. #
|
||||
# #
|
||||
# Notes The output string is in the ISO 8601 format, except for #
|
||||
# a space instead of a T between the date and time, to #
|
||||
# improve the readability. #
|
||||
# #
|
||||
# History #
|
||||
# 2015-06-11 JFL Created this routine. #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Now {
|
||||
Param (
|
||||
[Switch]$ms, # Append milliseconds
|
||||
[Switch]$ns # Append nanoseconds
|
||||
)
|
||||
$Date = Get-Date
|
||||
$now = ""
|
||||
$now += "{0:0000}-{1:00}-{2:00} " -f $Date.Year, $Date.Month, $Date.Day
|
||||
$now += "{0:00}:{1:00}:{2:00}" -f $Date.Hour, $Date.Minute, $Date.Second
|
||||
$nsSuffix = ""
|
||||
if ($ns) {
|
||||
if ("$($Date.TimeOfDay)" -match "\.\d\d\d\d\d\d") {
|
||||
$now += $matches[0]
|
||||
$ms = $false
|
||||
} else {
|
||||
$ms = $true
|
||||
$nsSuffix = "000"
|
||||
}
|
||||
}
|
||||
if ($ms) {
|
||||
$now += ".{0:000}$nsSuffix" -f $Date.MilliSecond
|
||||
}
|
||||
return $now
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Log #
|
||||
# #
|
||||
# Description Log a string into the PSService.log file #
|
||||
# #
|
||||
# Arguments A string #
|
||||
# #
|
||||
# Notes Prefixes the string with a timestamp and the user name. #
|
||||
# (Except if the string is empty: Then output a blank line.)#
|
||||
# #
|
||||
# History #
|
||||
# 2016-06-05 JFL Also prepend the Process ID. #
|
||||
# 2016-06-08 JFL Allow outputing blank lines. #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Log () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
|
||||
[String]$string
|
||||
)
|
||||
if (!(Test-Path $logDir)) {
|
||||
New-Item -ItemType directory -Path $logDir | Out-Null
|
||||
}
|
||||
if ($String.length) {
|
||||
$string = "$(Now) $pid $userName $string"
|
||||
}
|
||||
if($Debug) {
|
||||
Write-Host $string
|
||||
}
|
||||
$string | Out-File -Encoding ASCII -Append "$logFile"
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Start-PSThread #
|
||||
# #
|
||||
# Description Start a new PowerShell thread #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes Returns a thread description object. #
|
||||
# The completion can be tested in $_.Handle.IsCompleted #
|
||||
# Alternative: Use a thread completion event. #
|
||||
# #
|
||||
# References #
|
||||
# https://learn-powershell.net/tag/runspace/ #
|
||||
# https://learn-powershell.net/2013/04/19/sharing-variables-and-live-objects-between-powershell-runspaces/
|
||||
# http://www.codeproject.com/Tips/895840/Multi-Threaded-PowerShell-Cookbook
|
||||
# #
|
||||
# History #
|
||||
# 2016-06-08 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
$PSThreadCount = 0 # Counter of PSThread IDs generated so far
|
||||
$PSThreadList = @{} # Existing PSThreads indexed by Id
|
||||
|
||||
Function Get-PSThread () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
|
||||
[int[]]$Id = $PSThreadList.Keys # List of thread IDs
|
||||
)
|
||||
$Id | % { $PSThreadList.$_ }
|
||||
}
|
||||
|
||||
Function Start-PSThread () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ScriptBlock]$ScriptBlock, # The script block to run in a new thread
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$Name = "", # Optional thread name. Default: "PSThread$Id"
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$Event = "", # Optional thread completion event name. Default: None
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Hashtable]$Variables = @{}, # Optional variables to copy into the script context.
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String[]]$Functions = @(), # Optional functions to copy into the script context.
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Object[]]$Arguments = @() # Optional arguments to pass to the script.
|
||||
)
|
||||
|
||||
$Id = $script:PSThreadCount
|
||||
$script:PSThreadCount += 1
|
||||
if (!$Name.Length) {
|
||||
$Name = "PSThread$Id"
|
||||
}
|
||||
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
|
||||
foreach ($VarName in $Variables.Keys) { # Copy the specified variables into the script initial context
|
||||
$value = $Variables.$VarName
|
||||
Write-Debug "Adding variable $VarName=[$($Value.GetType())]$Value"
|
||||
$var = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry($VarName, $value, "")
|
||||
$InitialSessionState.Variables.Add($var)
|
||||
}
|
||||
foreach ($FuncName in $Functions) { # Copy the specified functions into the script initial context
|
||||
$Body = Get-Content function:$FuncName
|
||||
Write-Debug "Adding function $FuncName () {$Body}"
|
||||
$func = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry($FuncName, $Body)
|
||||
$InitialSessionState.Commands.Add($func)
|
||||
}
|
||||
$RunSpace = [RunspaceFactory]::CreateRunspace($InitialSessionState)
|
||||
$RunSpace.Open()
|
||||
$PSPipeline = [powershell]::Create()
|
||||
$PSPipeline.Runspace = $RunSpace
|
||||
$PSPipeline.AddScript($ScriptBlock) | Out-Null
|
||||
$Arguments | % {
|
||||
Write-Debug "Adding argument [$($_.GetType())]'$_'"
|
||||
$PSPipeline.AddArgument($_) | Out-Null
|
||||
}
|
||||
$Handle = $PSPipeline.BeginInvoke() # Start executing the script
|
||||
if ($Event.Length) { # Do this after BeginInvoke(), to avoid getting the start event.
|
||||
Register-ObjectEvent $PSPipeline -EventName InvocationStateChanged -SourceIdentifier $Name -MessageData $Event
|
||||
}
|
||||
$PSThread = New-Object PSObject -Property @{
|
||||
Id = $Id
|
||||
Name = $Name
|
||||
Event = $Event
|
||||
RunSpace = $RunSpace
|
||||
PSPipeline = $PSPipeline
|
||||
Handle = $Handle
|
||||
} # Return the thread description variables
|
||||
$script:PSThreadList[$Id] = $PSThread
|
||||
$PSThread
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Receive-PSThread #
|
||||
# #
|
||||
# Description Get the result of a thread, and optionally clean it up #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes #
|
||||
# #
|
||||
# History #
|
||||
# 2016-06-08 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Receive-PSThread () {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
|
||||
[PSObject]$PSThread, # Thread descriptor object
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Switch]$AutoRemove # If $True, remove the PSThread object
|
||||
)
|
||||
Process {
|
||||
if ($PSThread.Event -and $AutoRemove) {
|
||||
Unregister-Event -SourceIdentifier $PSThread.Name
|
||||
try {
|
||||
Get-Event -SourceIdentifier $PSThread.Name -ErrorAction SilentlyContinue | Remove-Event # Flush remaining events
|
||||
} catch {
|
||||
$_ # Output the thread pipeline error
|
||||
}
|
||||
}
|
||||
try {
|
||||
$PSThread.PSPipeline.EndInvoke($PSThread.Handle) # Output the thread pipeline output
|
||||
} catch {
|
||||
$_ # Output the thread pipeline error
|
||||
}
|
||||
if ($AutoRemove) {
|
||||
$PSThread.RunSpace.Close()
|
||||
$PSThread.PSPipeline.Dispose()
|
||||
$PSThreadList.Remove($PSThread.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Function Remove-PSThread () {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=0)]
|
||||
[PSObject]$PSThread # Thread descriptor object
|
||||
)
|
||||
Process {
|
||||
$_ | Receive-PSThread -AutoRemove | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Send-PipeMessage #
|
||||
# #
|
||||
# Description Send a message to a named pipe #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes #
|
||||
# #
|
||||
# History #
|
||||
# 2016-05-25 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Send-PipeMessage () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$PipeName, # Named pipe name
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$Message # Message string
|
||||
)
|
||||
$PipeDir = [System.IO.Pipes.PipeDirection]::Out
|
||||
$PipeOpt = [System.IO.Pipes.PipeOptions]::Asynchronous
|
||||
|
||||
$pipe = $null # Named pipe stream
|
||||
$sw = $null # Stream Writer
|
||||
try {
|
||||
$pipe = new-object System.IO.Pipes.NamedPipeClientStream(".", $PipeName, $PipeDir, $PipeOpt)
|
||||
$sw = new-object System.IO.StreamWriter($pipe)
|
||||
$pipe.Connect(1000)
|
||||
if (!$pipe.IsConnected) {
|
||||
throw "Failed to connect client to pipe $pipeName"
|
||||
}
|
||||
$sw.AutoFlush = $true
|
||||
$sw.WriteLine($Message)
|
||||
} catch {
|
||||
Log "Error sending pipe $pipeName message: $_"
|
||||
} finally {
|
||||
if ($sw) {
|
||||
$sw.Dispose() # Release resources
|
||||
$sw = $null # Force the PowerShell garbage collector to delete the .net object
|
||||
}
|
||||
if ($pipe) {
|
||||
$pipe.Dispose() # Release resources
|
||||
$pipe = $null # Force the PowerShell garbage collector to delete the .net object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Receive-PipeMessage #
|
||||
# #
|
||||
# Description Wait for a message from a named pipe #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes I tried keeping the pipe open between client connections, #
|
||||
# but for some reason everytime the client closes his end #
|
||||
# of the pipe, this closes the server end as well. #
|
||||
# Any solution on how to fix this would make the code #
|
||||
# more efficient. #
|
||||
# #
|
||||
# History #
|
||||
# 2016-05-25 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Receive-PipeMessage () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$PipeName # Named pipe name
|
||||
)
|
||||
$PipeDir = [System.IO.Pipes.PipeDirection]::In
|
||||
$PipeOpt = [System.IO.Pipes.PipeOptions]::Asynchronous
|
||||
$PipeMode = [System.IO.Pipes.PipeTransmissionMode]::Message
|
||||
|
||||
try {
|
||||
$pipe = $null # Named pipe stream
|
||||
$pipe = New-Object system.IO.Pipes.NamedPipeServerStream($PipeName, $PipeDir, 1, $PipeMode, $PipeOpt)
|
||||
$sr = $null # Stream Reader
|
||||
$sr = new-object System.IO.StreamReader($pipe)
|
||||
$pipe.WaitForConnection()
|
||||
$Message = $sr.Readline()
|
||||
$Message
|
||||
} catch {
|
||||
Log "Error receiving pipe message: $_"
|
||||
} finally {
|
||||
if ($sr) {
|
||||
$sr.Dispose() # Release resources
|
||||
$sr = $null # Force the PowerShell garbage collector to delete the .net object
|
||||
}
|
||||
if ($pipe) {
|
||||
$pipe.Dispose() # Release resources
|
||||
$pipe = $null # Force the PowerShell garbage collector to delete the .net object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Start-PipeHandlerThread #
|
||||
# #
|
||||
# Description Start a new thread waiting for control messages on a pipe #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes The pipe handler script uses function Receive-PipeMessage.#
|
||||
# This function must be copied into the thread context. #
|
||||
# #
|
||||
# The other functions and variables copied into that thread #
|
||||
# context are not strictly necessary, but are useful for #
|
||||
# debugging possible issues. #
|
||||
# #
|
||||
# History #
|
||||
# 2016-06-07 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
$pipeThreadName = "Control Pipe Handler"
|
||||
|
||||
Function Start-PipeHandlerThread () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$pipeName, # Named pipe name
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$Event = "ControlMessage" # Event message
|
||||
)
|
||||
Start-PSThread -Variables @{ # Copy variables required by function Log() into the thread context
|
||||
logDir = $logDir
|
||||
logFile = $logFile
|
||||
userName = $userName
|
||||
} -Functions Now, Log, Receive-PipeMessage -ScriptBlock {
|
||||
Param($pipeName, $pipeThreadName)
|
||||
try {
|
||||
Receive-PipeMessage "$pipeName" # Blocks the thread until the next message is received from the pipe
|
||||
} catch {
|
||||
Log "$pipeThreadName # Error: $_"
|
||||
throw $_ # Push the error back to the main thread
|
||||
}
|
||||
} -Name $pipeThreadName -Event $Event -Arguments $pipeName, $pipeThreadName
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Receive-PipeHandlerThread #
|
||||
# #
|
||||
# Description Get what the pipe handler thread received #
|
||||
# #
|
||||
# Arguments See the Param() block #
|
||||
# #
|
||||
# Notes #
|
||||
# #
|
||||
# History #
|
||||
# 2016-06-07 JFL Created this function #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
Function Receive-PipeHandlerThread () {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[PSObject]$pipeThread # Thread descriptor
|
||||
)
|
||||
Receive-PSThread -PSThread $pipeThread -AutoRemove
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function $source #
|
||||
# #
|
||||
# Description C# source of the PSService.exe stub #
|
||||
# #
|
||||
# Arguments #
|
||||
# #
|
||||
# Notes The lines commented with "SET STATUS" and "EVENT LOG" are #
|
||||
# optional. (Or blocks between "// SET STATUS [" and #
|
||||
# "// SET STATUS ]" comments.) #
|
||||
# SET STATUS lines are useful only for services with a long #
|
||||
# startup time. #
|
||||
# EVENT LOG lines are useful for debugging the service. #
|
||||
# #
|
||||
# History #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
$scriptCopyCname = $scriptCopy -replace "\\", "\\" # Double backslashes. (The first \\ is a regexp with \ escaped; The second is a plain string.)
|
||||
$source = @"
|
||||
using System;
|
||||
using System.ServiceProcess;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices; // SET STATUS
|
||||
using System.ComponentModel; // SET STATUS
|
||||
|
||||
public enum ServiceType : int { // SET STATUS [
|
||||
SERVICE_WIN32_OWN_PROCESS = 0x00000010,
|
||||
SERVICE_WIN32_SHARE_PROCESS = 0x00000020,
|
||||
}; // SET STATUS ]
|
||||
|
||||
public enum ServiceState : int { // SET STATUS [
|
||||
SERVICE_STOPPED = 0x00000001,
|
||||
SERVICE_START_PENDING = 0x00000002,
|
||||
SERVICE_STOP_PENDING = 0x00000003,
|
||||
SERVICE_RUNNING = 0x00000004,
|
||||
SERVICE_CONTINUE_PENDING = 0x00000005,
|
||||
SERVICE_PAUSE_PENDING = 0x00000006,
|
||||
SERVICE_PAUSED = 0x00000007,
|
||||
}; // SET STATUS ]
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)] // SET STATUS [
|
||||
public struct ServiceStatus {
|
||||
public ServiceType dwServiceType;
|
||||
public ServiceState dwCurrentState;
|
||||
public int dwControlsAccepted;
|
||||
public int dwWin32ExitCode;
|
||||
public int dwServiceSpecificExitCode;
|
||||
public int dwCheckPoint;
|
||||
public int dwWaitHint;
|
||||
}; // SET STATUS ]
|
||||
|
||||
public enum Win32Error : int { // WIN32 errors that we may need to use
|
||||
NO_ERROR = 0,
|
||||
ERROR_APP_INIT_FAILURE = 575,
|
||||
ERROR_FATAL_APP_EXIT = 713,
|
||||
ERROR_SERVICE_NOT_ACTIVE = 1062,
|
||||
ERROR_EXCEPTION_IN_SERVICE = 1064,
|
||||
ERROR_SERVICE_SPECIFIC_ERROR = 1066,
|
||||
ERROR_PROCESS_ABORTED = 1067,
|
||||
};
|
||||
|
||||
public class Service_$serviceName : ServiceBase { // $serviceName may begin with a digit; The class name must begin with a letter
|
||||
private System.Diagnostics.EventLog eventLog; // EVENT LOG
|
||||
private ServiceStatus serviceStatus; // SET STATUS
|
||||
|
||||
public Service_$serviceName() {
|
||||
ServiceName = "$serviceName";
|
||||
CanStop = true;
|
||||
CanPauseAndContinue = false;
|
||||
AutoLog = true;
|
||||
|
||||
eventLog = new System.Diagnostics.EventLog(); // EVENT LOG [
|
||||
if (!System.Diagnostics.EventLog.SourceExists(ServiceName)) {
|
||||
System.Diagnostics.EventLog.CreateEventSource(ServiceName, "$logName");
|
||||
}
|
||||
eventLog.Source = ServiceName;
|
||||
eventLog.Log = "$logName"; // EVENT LOG ]
|
||||
EventLog.WriteEntry(ServiceName, "$exeName $serviceName()"); // EVENT LOG
|
||||
}
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError=true)] // SET STATUS
|
||||
private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);
|
||||
|
||||
protected override void OnStart(string [] args) {
|
||||
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Entry. Starting script '$scriptCopyCname' -Start"); // EVENT LOG
|
||||
// Set the service state to Start Pending. // SET STATUS [
|
||||
// Only useful if the startup time is long. Not really necessary here for a 2s startup time.
|
||||
serviceStatus.dwServiceType = ServiceType.SERVICE_WIN32_OWN_PROCESS;
|
||||
serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
|
||||
serviceStatus.dwWin32ExitCode = 0;
|
||||
serviceStatus.dwWaitHint = 2000; // It takes about 2 seconds to start PowerShell
|
||||
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS ]
|
||||
// Start a child process with another copy of this script
|
||||
try {
|
||||
Process p = new Process();
|
||||
// Redirect the output stream of the child process.
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.FileName = "PowerShell.exe";
|
||||
p.StartInfo.Arguments = "-ExecutionPolicy Bypass -c & '$scriptCopyCname' -Start"; // Works if path has spaces, but not if it contains ' quotes.
|
||||
p.Start();
|
||||
// Read the output stream first and then wait. (To avoid deadlocks says Microsoft!)
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
// Wait for the completion of the script startup code, that launches the -Service instance
|
||||
p.WaitForExit();
|
||||
if (p.ExitCode != 0) throw new Win32Exception((int)(Win32Error.ERROR_APP_INIT_FAILURE));
|
||||
// Success. Set the service state to Running. // SET STATUS
|
||||
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING; // SET STATUS
|
||||
} catch (Exception e) {
|
||||
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Failed to start $scriptCopyCname. " + e.Message, EventLogEntryType.Error); // EVENT LOG
|
||||
// Change the service state back to Stopped. // SET STATUS [
|
||||
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;
|
||||
Win32Exception w32ex = e as Win32Exception; // Try getting the WIN32 error code
|
||||
if (w32ex == null) { // Not a Win32 exception, but maybe the inner one is...
|
||||
w32ex = e.InnerException as Win32Exception;
|
||||
}
|
||||
if (w32ex != null) { // Report the actual WIN32 error
|
||||
serviceStatus.dwWin32ExitCode = w32ex.NativeErrorCode;
|
||||
} else { // Make up a reasonable reason
|
||||
serviceStatus.dwWin32ExitCode = (int)(Win32Error.ERROR_APP_INIT_FAILURE);
|
||||
} // SET STATUS ]
|
||||
} finally {
|
||||
serviceStatus.dwWaitHint = 0; // SET STATUS
|
||||
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
|
||||
EventLog.WriteEntry(ServiceName, "$exeName OnStart() // Exit"); // EVENT LOG
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStop() {
|
||||
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Entry"); // EVENT LOG
|
||||
// Start a child process with another copy of ourselves
|
||||
Process p = new Process();
|
||||
// Redirect the output stream of the child process.
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.FileName = "PowerShell.exe";
|
||||
p.StartInfo.Arguments = "-c & '$scriptCopyCname' -Stop"; // Works if path has spaces, but not if it contains ' quotes.
|
||||
p.Start();
|
||||
// Read the output stream first and then wait.
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
// Wait for the PowerShell script to be fully stopped.
|
||||
p.WaitForExit();
|
||||
// Change the service state back to Stopped. // SET STATUS
|
||||
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED; // SET STATUS
|
||||
SetServiceStatus(ServiceHandle, ref serviceStatus); // SET STATUS
|
||||
EventLog.WriteEntry(ServiceName, "$exeName OnStop() // Exit"); // EVENT LOG
|
||||
}
|
||||
|
||||
public static void Main() {
|
||||
System.ServiceProcess.ServiceBase.Run(new Service_$serviceName());
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
#-----------------------------------------------------------------------------#
|
||||
# #
|
||||
# Function Main #
|
||||
# #
|
||||
# Description Execute the specified actions #
|
||||
# #
|
||||
# Arguments See the Param() block at the top of this script #
|
||||
# #
|
||||
# Notes #
|
||||
# #
|
||||
# History #
|
||||
# #
|
||||
#-----------------------------------------------------------------------------#
|
||||
|
||||
# Check if we're running as a real user, or as the SYSTEM = As a service
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$userName = $identity.Name # Ex: "NT AUTHORITY\SYSTEM" or "Domain\Administrator"
|
||||
$authority,$name = $username -split "\\"
|
||||
$isSystem = $identity.IsSystem # Do not test ($userName -eq "NT AUTHORITY\SYSTEM"), as this fails in non-English systems.
|
||||
# Log "# `$userName = `"$userName`" ; `$isSystem = $isSystem"
|
||||
|
||||
if ($Setup) {Log ""} # Insert one blank line to separate test sessions logs
|
||||
Log $MyInvocation.Line # The exact command line that was used to start us
|
||||
|
||||
# The following commands write to the event log, but we need to make sure the PSService source is defined.
|
||||
New-EventLog -LogName $logName -Source $serviceName -ea SilentlyContinue
|
||||
|
||||
# Workaround for PowerShell v2 bug: $PSCmdlet Not yet defined in Param() block
|
||||
$Status = ($PSCmdlet.ParameterSetName -eq 'Status')
|
||||
|
||||
# Configuration Path, create if missing and make a default configuration file.
|
||||
$hassIoConfiguration = "$($env:ALLUSERSPROFILE)\hassio"
|
||||
if(!(Test-Path -Path $hassIoConfiguration)) {
|
||||
New-Item -Path $hassIoConfiguration -ItemType Directory | Out-Null
|
||||
|
||||
# Create default configuration.
|
||||
$defaultConfiguration = "{
|
||||
`"datapath`":`"/usr/share/hassio`",
|
||||
`"home_assistant_docker`":`"homeassistant/qemux86-64-homeassistant`",
|
||||
`"hassio_supervisor_docker`":`"homeassistant/amd64-hassio-supervisor`"
|
||||
}"
|
||||
$defaultConfiguration | Set-Content -Path (Join-Path $hassIoConfiguration 'configuration.json') -Encoding UTF8
|
||||
}
|
||||
|
||||
$runtimeConfiguration = Get-Content -Path (Join-Path $hassIoConfiguration 'configuration.json') -Encoding UTF8 | ConvertFrom-Json
|
||||
|
||||
if ($Start) { # Start the service
|
||||
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
|
||||
# Do whatever is necessary to start the service script instance
|
||||
Log "$scriptName -Start: Starting script '$scriptFullName' -Service"
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1001 -EntryType Information -Message "$scriptName -Start: Starting script '$scriptFullName' -Service"
|
||||
Start-Process PowerShell.exe -ArgumentList ("-c & '$scriptFullName' -Service")
|
||||
} else {
|
||||
Write-Verbose "Starting service $serviceName"
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1002 -EntryType Information -Message "$scriptName -Start: Starting service $serviceName"
|
||||
Start-Service $serviceName # Ask Service Control Manager to start it
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($Stop) { # Stop the service
|
||||
if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
|
||||
# Do whatever is necessary to stop the service script instance
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1003 -EntryType Information -Message "$scriptName -Stop: Stopping script $scriptName -Service"
|
||||
Log "$scriptName -Stop: Stopping script $scriptName -Service"
|
||||
# Send an exit message to the service instance
|
||||
Send-PipeMessage $pipeName "exit"
|
||||
} else {
|
||||
Write-Verbose "Stopping service $serviceName"
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1004 -EntryType Information -Message "$scriptName -Stop: Stopping service $serviceName"
|
||||
Stop-Service $serviceName # Ask Service Control Manager to stop it
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($Restart) { # Restart the service
|
||||
& $scriptFullName -Stop
|
||||
& $scriptFullName -Start
|
||||
return
|
||||
}
|
||||
|
||||
if($ViewLog) {
|
||||
notepad $logFile
|
||||
}
|
||||
|
||||
if($Attach) {
|
||||
docker start --attach hassio_supervisor
|
||||
}
|
||||
|
||||
|
||||
if ($Status) { # Get the current service status
|
||||
$spid = $null
|
||||
$processes = @(Get-WmiObject Win32_Process -filter "Name = 'powershell.exe'" | Where-Object {
|
||||
$_.CommandLine -match ".*$scriptCopyCname.*-Service"
|
||||
})
|
||||
foreach ($process in $processes) { # There should be just one, but be prepared for surprises.
|
||||
$spid = $process.ProcessId
|
||||
Write-Verbose "$serviceName Process ID = $spid"
|
||||
}
|
||||
# if (Test-Path "HKLM:\SYSTEM\CurrentControlSet\services\$serviceName") {}
|
||||
try {
|
||||
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
|
||||
} catch {
|
||||
"Not Installed"
|
||||
return
|
||||
}
|
||||
$pss.Status
|
||||
if (($pss.Status -eq "Running") -and (!$spid)) { # This happened during the debugging phase
|
||||
Write-Error "The Service Control Manager thinks $serviceName is started, but $serviceName.ps1 -Service is not running."
|
||||
exit 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($Setup) { # Install the service
|
||||
# Check if it's necessary
|
||||
try {
|
||||
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
|
||||
# Check if this script is newer than the installed copy.
|
||||
if ((Get-Item $scriptCopy -ea SilentlyContinue).LastWriteTime -lt (Get-Item $scriptFullName -ea SilentlyContinue).LastWriteTime) {
|
||||
Write-Verbose "Service $serviceName is already Installed, but requires upgrade"
|
||||
& $scriptFullName -Remove
|
||||
throw "continue"
|
||||
} else {
|
||||
Write-Verbose "Service $serviceName is already Installed, and up-to-date"
|
||||
}
|
||||
exit 0
|
||||
} catch {
|
||||
# This is the normal case here. Do not throw or write any error!
|
||||
Write-Debug "Installation is necessary" # Also avoids a ScriptAnalyzer warning
|
||||
# And continue with the installation.
|
||||
}
|
||||
if (!(Test-Path $installDir)) {
|
||||
New-Item -ItemType directory -Path $installDir | Out-Null
|
||||
}
|
||||
# Copy the service script into the installation directory
|
||||
if ($ScriptFullName -ne $scriptCopy) {
|
||||
Write-Verbose "Installing $scriptCopy"
|
||||
Copy-Item $ScriptFullName $scriptCopy
|
||||
}
|
||||
# Generate the service .EXE from the C# source embedded in this script
|
||||
try {
|
||||
Write-Verbose "Compiling $exeFullName"
|
||||
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $exeFullName -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false
|
||||
} catch {
|
||||
$msg = $_.Exception.Message
|
||||
Write-error "Failed to create the $exeFullName service stub. $msg"
|
||||
exit 1
|
||||
}
|
||||
# Register the service
|
||||
Write-Verbose "Registering service $serviceName"
|
||||
$pss = New-Service $serviceName $exeFullName -DisplayName $serviceDisplayName -Description $ServiceDescription -StartupType Automatic
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ($Remove) { # Uninstall the service
|
||||
# Check if it's necessary
|
||||
try {
|
||||
$pss = Get-Service $serviceName -ea stop # Will error-out if not installed
|
||||
} catch {
|
||||
Write-Verbose "Already uninstalled"
|
||||
return
|
||||
}
|
||||
Stop-Service $serviceName # Make sure it's stopped
|
||||
# In the absence of a Remove-Service applet, use sc.exe instead.
|
||||
Write-Verbose "Removing service $serviceName"
|
||||
$msg = sc.exe delete $serviceName
|
||||
if ($LastExitCode) {
|
||||
Write-Error "Failed to remove the service ${serviceName}: $msg"
|
||||
exit 1
|
||||
} else {
|
||||
Write-Verbose $msg
|
||||
}
|
||||
# Remove the installed files
|
||||
if (Test-Path $installDir) {
|
||||
foreach ($ext in ("exe", "pdb", "ps1")) {
|
||||
$file = "$installDir\$serviceName.$ext"
|
||||
if (Test-Path $file) {
|
||||
Write-Verbose "Deleting file $file"
|
||||
Remove-Item $file
|
||||
}
|
||||
}
|
||||
if (!(@(Get-ChildItem $installDir -ea SilentlyContinue)).Count) {
|
||||
Write-Verbose "Removing directory $installDir"
|
||||
Remove-Item $installDir
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($Control) { # Send a control message to the service
|
||||
Send-PipeMessage $pipeName $control
|
||||
}
|
||||
|
||||
if ($Service) { # Run the service
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1005 -EntryType Information -Message "$scriptName -Service # Beginning background job"
|
||||
# Do the service background job
|
||||
try {
|
||||
# Start the control pipe handler thread
|
||||
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
|
||||
|
||||
$HASSIO_IMAGE_ID = $(docker inspect --format='{{.Id}}' $runtimeConfiguration.hassio_supervisor_docker)
|
||||
$HASSIO_CONTAINER_IMAGE_ID=$(docker inspect --format='{{.Image}}' hassio_supervisor)
|
||||
|
||||
if("$HASSIO_IMAGE_ID" -eq "$HASSIO_CONTAINER_IMAGE_ID") {
|
||||
# No version or name issues, ensure started.
|
||||
docker start hassio_supervisor
|
||||
} else {
|
||||
|
||||
# Remove existing supervisor instance.
|
||||
docker rm --force hassio_supervisor
|
||||
|
||||
# Prepare arguments for new instance.
|
||||
$arguments = @(
|
||||
"run",
|
||||
"--name",
|
||||
"hassio_supervisor",
|
||||
"-v",
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
"-v",
|
||||
"/var/run/hassio-hc.sock:/var/run/hassio-hc.sock",
|
||||
"-v",
|
||||
"$($runtimeConfiguration.datapath):/data",
|
||||
"-e",
|
||||
"SUPERVISOR_SHARE=$($runtimeConfiguration.datapath)",
|
||||
"-e",
|
||||
"SUPERVISOR_NAME=hassio_supervisor",
|
||||
"-e",
|
||||
"HOMEASSISTANT_REPOSITORY=$($runtimeConfiguration.home_assistant_docker)",
|
||||
$($runtimeConfiguration.hassio_supervisor_docker)
|
||||
)
|
||||
|
||||
Start-Process -FilePath docker -ArgumentList $arguments
|
||||
}
|
||||
# Now enter the main service event loop
|
||||
do { # Keep running until told to exit by the -Stop handler
|
||||
$event = Wait-Event # Wait for the next incoming event
|
||||
$source = $event.SourceIdentifier
|
||||
$message = $event.MessageData
|
||||
$eventTime = $event.TimeGenerated.TimeofDay
|
||||
Write-Debug "Event at $eventTime from ${source}: $message"
|
||||
$event | Remove-Event # Flush the event from the queue
|
||||
switch ($message) {
|
||||
"ControlMessage" { # Required. Message received by the control pipe thread
|
||||
$state = $event.SourceEventArgs.InvocationStateInfo.state
|
||||
Log "$script -Service # Thread $source state changed to $state"
|
||||
switch ($state) {
|
||||
"Completed" {
|
||||
$message = Receive-PipeHandlerThread $pipeThread
|
||||
Log "$scriptName -Service # Received control message: $Message"
|
||||
Log "$scriptName -Service # Pipe Thread: $pipeThread"
|
||||
switch ($message) {
|
||||
"info" {
|
||||
$HOSTNAME = $env:COMPUTERNAME
|
||||
$osInfo = Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, ServicePackMajorVersion, OSArchitecture, CSName, WindowsDirectory
|
||||
$GENERIC_HC_VERSION= (ConvertFrom-Json (Invoke-WebRequest -Uri $URL_VERSION).Content).generic
|
||||
$FEATURES="[`"shutdown`", `"reboot`", `"update`"]"
|
||||
"{ `"type`": `"generic`", `"version`": `"$VERSION`", `"last_version`": `"$GENERIC_HC_VERSION`", `"hostname`": `"$HOSTNAME`", `"features`": $FEATURES, `"os`": `"$($osInfo.Caption)`" }" |
|
||||
Set-Content -Path (Join-Path $hassIoConfiguration 'info.json') -Encoding UTF8
|
||||
break
|
||||
}
|
||||
"reboot" {
|
||||
Restart-Computer
|
||||
break
|
||||
}
|
||||
"shutdown" {
|
||||
Stop-Computer
|
||||
break
|
||||
}
|
||||
Default {
|
||||
Log "$scriptName -Service # Unexpected event from ${source}: $Message"
|
||||
}
|
||||
}
|
||||
|
||||
if ($message -ne "exit") { # Start another thread waiting for control messages
|
||||
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
|
||||
}
|
||||
}
|
||||
"Failed" {
|
||||
$error = Receive-PipeHandlerThread $pipeThread
|
||||
Log "$scriptName -Service # $source thread failed: $error"
|
||||
Start-Sleep 1 # Avoid getting too many errors
|
||||
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" # Retry
|
||||
}
|
||||
}
|
||||
}
|
||||
default { # Should not happen
|
||||
Log "$scriptName -Service # Unexpected event from ${source}: $Message"
|
||||
}
|
||||
}
|
||||
} while ($message -ne "exit")
|
||||
} catch { # An exception occurred while runnning the service
|
||||
$msg = $_.Exception.Message
|
||||
$line = $_.InvocationInfo.ScriptLineNumber
|
||||
Log "$scriptName -Service # Error at line ${line}: $msg"
|
||||
} finally { # Invoked in all cases: Exception or normally by -Stop
|
||||
|
||||
# Terminate the control pipe handler thread
|
||||
Get-PSThread | Remove-PSThread # Remove all remaining threads
|
||||
# Flush all leftover events (There may be some that arrived after we exited the while event loop, but before we unregistered the events)
|
||||
$events = Get-Event | Remove-Event
|
||||
# Log a termination event, no matter what the cause is.
|
||||
Write-EventLog -LogName $logName -Source $serviceName -EventId 1006 -EntryType Information -Message "$script -Service # Exiting"
|
||||
Log "$scriptName -Service # Exiting"
|
||||
}
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue