Hubot.psm1
# Defines the values for the resource's Ensure property. enum Ensure { # The resource must be absent. Absent # The resource must be present. Present } class HubotHelpers { [string] RefreshPathVariable () { $updatedPath = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") return $updatedPath } [bool] CheckPathExists ([string]$Path) { if (Test-Path -Path $Path) { Write-Verbose "Directory $($Path) exists." return $true } else { Write-Verbose "Directory $($Path) exists." return $false } } [PSCustomObject] RunProcess([string]$FilePath, [string]$ArgumentList, [string]$WorkingDirectory) { $env:Path = [HubotHelpers]::new().RefreshPathVariable() $pinfo = New-Object System.Diagnostics.ProcessStartInfo if (-not([string]::IsNullOrEmpty($WorkingDirectory))) { $pinfo.WorkingDirectory = $WorkingDirectory } $pinfo.FileName = $FilePath $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Unicode $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Unicode $pinfo.Arguments = $ArgumentList $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $stdout = $p.StandardOutput.ReadToEnd() $stderr = $p.StandardError.ReadToEnd() $output = @{} $output.filepath = $FilePath $output.arg = $ArgumentList $output.workingdirectory = $WorkingDirectory $output.stdout = $stdout $output.stderr = $stderr $output.exitcode = $p.ExitCode $returnObj = New-Object -Property $output -TypeName PSCustomObject Write-Verbose $returnObj return $returnObj } [string] GetNSSMPath () { if (Test-Path -Path 'C:\nssm') { # get latest version installed $path = ((Get-ChildItem -Path C:\nssm\*\win64\nssm.exe)[-1]).FullName Write-Verbose "Found nssm at $($path)" return $path } else { throw 'NSSM folder cannot be found at C:\nssm' } } } [DscResource()] class HubotInstall { # A DSC resource must define at least one key property. [DscProperty(Key)] [string]$BotPath [DscProperty(Mandatory)] [Ensure]$Ensure [DscProperty(NotConfigurable)] [String]$NodeModulesPath # Gets the resource's current state. [HubotInstall] Get() { $GetObject = [HubotInstall]::new() $GetObject.BotPath = $this.BotPath $GetObject.Ensure = $this.Ensure $GetObject.NodeModulesPath = Join-Path -Path $this.BotPath -ChildPath 'node_modules' if([HubotHelpers]::new().CheckPathExists($GetObject.NodeModulesPath)) { $GetObject.Ensure = [Ensure]::Present } else { $GetObject.Ensure = [Ensure]::Absent } return $GetObject } # Sets the desired state of the resource. [void] Set() { $Helpers = [HubotHelpers]::new() $env:Path = $Helpers.RefreshPathVariable() if (!($Helpers.CheckPathExists($this.BotPath))) { throw "The path $($this.BotPath) must exist and contain a Hubot installation in it. You can clone one from here: https://github.com/MattHodge/HubotWindows" } if (Get-Command -CommandType Application -Name npm -ErrorAction SilentlyContinue) { $npmPath = (Get-Command -CommandType Application -Name npm)[0].Source } else { throw "npm cannot be found. Cannot continue." } if ($this.Ensure -eq [Ensure]::Present) { $npmCmd = 'install' } else { $npmCmd = 'uninstall' } Write-Verbose -Message "$($npmCmd)ing CoffeeScript at $($this.BotPath)" Start-Process -FilePath $npmPath -ArgumentList "$($npmCmd) coffee-script" -Wait Write-Verbose "$($npmCmd)ing all required npm modules" Start-Process -FilePath $npmPath -ArgumentList $npmCmd -Wait if ($this.Ensure -eq [Ensure]::Absent) { Remove-Item -Path $this.NodeModulesPath -Force } } # Tests if the resource is in the desired state. [bool] Test() { $TestObject = $This.Get() # present case if ($TestObject.Ensure -eq [Ensure]::Present) { return $true } # absent case else { return $false } } } [DscResource()] class HubotInstallService { # Path where the Hubot is located [DscProperty(Key)] [string]$BotPath # Name for the Hubot service [DscProperty(Mandatory)] [string]$ServiceName # Credential to run the service under [DscProperty()] [PSCredential]$Credential # Bot adapter for Hubot to be used. Used as a paramater to start the server (-a $botadapter) [DscProperty(Mandatory)] [string]$BotAdapter [DscProperty(Mandatory)] [Ensure]$Ensure [DscProperty(NotConfigurable)] [Boolean]$State_ServiceExists [DscProperty(NotConfigurable)] [Boolean]$State_ServiceRunning [DscProperty(NotConfigurable)] [string]$NSSMAppParameters [DscProperty(NotConfigurable)] [Boolean]$State_NSSMAppParameters # Gets the resource's current state. [HubotInstallService] Get() { $Helpers = [HubotHelpers]::new() $GetObject = [HubotInstallService]::new() $GetObject.BotPath = $this.BotPath $GetObject.Ensure = $this.Ensure $GetObject.ServiceName = $this.ServiceName $GetObject.Credential = $this.Credential $GetObject.BotAdapter = $this.BotAdapter $GetObject.NSSMAppParameters = "/c .\bin\hubot.cmd -a $($this.BotAdapter)" # set default states to save having nested if statements $GetObject.State_ServiceExists = $false $GetObject.State_ServiceRunning = $false $GetObject.State_NSSMAppParameters = $false if (Get-Service -Name $this.ServiceName -ErrorAction SilentlyContinue) { $GetObject.State_ServiceExists = $true # Check service is running if ((Get-Service -Name $this.ServiceName).Status -eq 'Running') { $GetObject.State_ServiceRunning = $true } $nssmPath = $Helpers.GetNSSMPath() # check if appparams set correctly $currentAppParams = ($Helpers.RunProcess($nssmPath,"get $($this.ServiceName) AppParameters",$null)).stdout # need to use trim to remove white spaces if ([string]$currentAppParams.Trim() -eq [string]$GetObject.NSSMAppParameters) { $GetObject.State_NSSMAppParameters = $true } } return $GetObject } [void] Set() { $Helpers = [HubotHelpers]::new() $env:Path = $Helpers.RefreshPathVariable() $TestObject = $This.Get() $nssmPath = $Helpers.GetNSSMPath() if ($this.Ensure -eq [Ensure]::Present) { if ($TestObject.State_ServiceExists) { Write-Verbose "Removing old service" Stop-Service -Name $this.ServiceName -Force $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null } $botLogPath = Join-Path -Path $this.BotPath -ChildPath 'Logs' Write-Verbose "Creating bot logging path at $($botLogPath)" New-Item -Path $botLogPath -Force -ItemType Directory $arrayOfCmds = @( "install $($this.ServiceName) cmd.exe" "set $($this.ServiceName) AppDirectory $($this.BotPath)" "set $($this.ServiceName) AppParameters ""/c .\bin\hubot.cmd -a $($this.BotAdapter)""" "set $($this.ServiceName) AppStdout ""$($botLogPath)\$($this.ServiceName)_log.txt""" "set $($this.ServiceName) AppStderr ""$($botLogPath)\$($this.ServiceName)_error.txt""" "set $($this.ServiceName) AppDirectory $($this.BotPath)" "set $($this.ServiceName) AppRotateFiles 1" "set $($this.ServiceName) AppRotateOnline 1" "set $($this.ServiceName) AppRotateSeconds 86400" "set $($this.ServiceName) Description Hubot Service" "set $($this.ServiceName) Start SERVICE_AUTO_START" ) # if a credetial is passed with no password assume LocalSystem if ([string]::IsNullOrEmpty($this.Credential.UserName)) { Write-Verbose "No credential passed, using LocalSystem." $arrayOfCmds += "set $($this.ServiceName) ObjectName LocalSystem" } # if a credential is passed with a password else { Write-Verbose "Credential passed, using username $($this.Credential.UserName)." $arrayOfCmds += "set $($this.ServiceName) ObjectName .\$($this.Credential.UserName) $($this.Credential.GetNetworkCredential().Password)" } ForEach ($cmd in $arrayOfCmds) { # Replacing password so it doesn't show in debug logs Write-Verbose "Running NSSM $($cmd)".Replace($($this.Credential.GetNetworkCredential().Password),'* PASSWORD OBSCURED *') $Helpers.RunProcess($nssmPath,$cmd,$null) | Out-Null } Start-Service -Name $this.ServiceName } else { Write-Verbose "Removing Bot Service $($this.ServiceName)" Stop-Service -Name $this.ServiceName -Force -ErrorAction SilentlyContinue $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null } } # Tests if the resource is in the desired state. [bool] Test() { $TestObject = $This.Get() # present case if ($this.Ensure -eq [Ensure]::Present) { # If any of the possible states for the service are false, not in desired state return (-not($TestObject.psobject.Properties.Where({$PSItem.Name -like 'State_*'}).Value -contains $false)) } # absent case else { return (-not($TestObject.State_ServiceExists)) } } } |