├── .gitignore ├── .kitchen-hyperv.yml ├── .kitchen.yml ├── DSCResources └── HubotPrerequisites │ ├── HubotPrerequisites.psd1 │ └── HubotPrerequisites.schema.psm1 ├── Examples ├── HubotInstallService_Example.ps1 ├── HubotInstall_Example.ps1 └── dsc_configuration.ps1 ├── Hubot.psd1 ├── Hubot.psm1 ├── LICENSE ├── README.md ├── Tests ├── HubotHelpers_Unit_Tests.ps1 ├── HubotInstallService_Unit_Tests.ps1 ├── HubotInstall_Unit_Tests.ps1 ├── Hubot_MOF_Generation_Tests.ps1 ├── appveyor.pester.ps1 └── integration │ └── default │ └── pester │ └── default.tests.ps1 ├── appveyor.yml ├── build.ps1 └── psakeBuild.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | .kitchen 2 | Artifact -------------------------------------------------------------------------------- /.kitchen-hyperv.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: hyperv 4 | parent_vhd_folder: C:\VMs\Win2016TP5Core\Virtual Hard Disks 5 | parent_vhd_name: Win2016TP5Core.vhdx 6 | memory_startup_bytes: 2GB 7 | vm_switch: HyperVWifi 8 | vm_generation: 2 9 | transport: 10 | name: winrm 11 | username: Administrator 12 | password: L0C4L4dmin! 13 | winrm_transport: plaintext 14 | 15 | provisioner: 16 | name: dsc 17 | dsc_local_configuration_manager_version: wmf5 18 | dsc_local_configuration_manager: 19 | reboot_if_needed: true 20 | debug_mode: none 21 | allow_module_overwrite: true 22 | configuration_script_folder: examples 23 | configuration_script: dsc_configuration.ps1 24 | configuration_data_variable: configData 25 | configuration_name: Hubot 26 | modules_path: . 27 | modules_from_gallery: 28 | - cChoco 29 | - xPSDesiredStateConfiguration 30 | 31 | verifier: 32 | name: pester 33 | test_folder: Tests 34 | 35 | platforms: 36 | - name: Win2016TP5-wmf5 37 | driver: 38 | parent_vhd: C:\VMs\Win2016TP5Core\Virtual Hard Disks\Win2016TP5Core.vhdx 39 | provisioner: 40 | dsc_local_configuration_manager_version: wmf5 41 | 42 | suites: 43 | - name: default 44 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | communicator: winrm 5 | gui: true 6 | 7 | transport: 8 | name: winrm 9 | 10 | provisioner: 11 | name: dsc 12 | retry_on_exit_code: 13 | - 35 14 | dsc_local_configuration_manager_version: wmf5 15 | dsc_local_configuration_manager: 16 | action_after_reboot: StopConfiguration 17 | reboot_if_needed: false 18 | debug_mode: none 19 | allow_module_overwrite: true 20 | configuration_script_folder: examples 21 | configuration_script: dsc_configuration.ps1 22 | configuration_data_variable: configData 23 | configuration_name: Hubot 24 | modules_path: . 25 | modules_from_gallery: 26 | - xPSDesiredStateConfiguration 27 | 28 | platforms: 29 | - name: Win2016TP5Core 30 | driver: 31 | box: MattHodge/Win2016TP5Core 32 | gui: true 33 | provisioner: 34 | dsc_local_configuration_manager_version: wmf5 35 | 36 | verifier: 37 | name: pester 38 | test_folder: Tests 39 | 40 | suites: 41 | - name: default 42 | -------------------------------------------------------------------------------- /DSCResources/HubotPrerequisites/HubotPrerequisites.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattHodge/Hubot-DSC-Resource/6267c9fa6b5e724e734fbbb05c5bba4c0c2d67c2/DSCResources/HubotPrerequisites/HubotPrerequisites.psd1 -------------------------------------------------------------------------------- /DSCResources/HubotPrerequisites/HubotPrerequisites.schema.psm1: -------------------------------------------------------------------------------- 1 | Configuration HubotPrerequisites 2 | { 3 | param 4 | ( 5 | [Parameter(Mandatory=$true)] 6 | [ValidateSet("Present", "Absent")] 7 | [string] 8 | $Ensure 9 | ) 10 | 11 | # Import the module that defines custom resources 12 | Import-DscResource -ModuleName PSDesiredStateConfiguration 13 | Import-DscResource -Name MSFT_xRemoteFile -ModuleName xPSDesiredStateConfiguration 14 | 15 | $nodeFile = 'node-v5.12.0-x64.msi' 16 | $gitFile = 'Git-2.9.0-64-bit.exe' 17 | $nssmFile = 'nssm-2.24.zip' 18 | 19 | xRemoteFile dlNode 20 | { 21 | Uri = 'https://nodejs.org/dist/v5.12.0/node-v5.12.0-x64.msi' 22 | DestinationPath = "$($env:Temp)\$($nodeFile)" 23 | MatchSource = $false 24 | } 25 | 26 | Package nodejs 27 | { 28 | Ensure = $Ensure 29 | Path = "$($env:Temp)\$($nodeFile)" 30 | Name = "Node.js" 31 | ProductId = "CE9C30F7-140C-4A6B-95C8-8304CCBF0145" 32 | Arguments = '/qn ALLUSERS=1 REBOOT=ReallySuppress' 33 | DependsOn = '[xRemoteFile]dlNode' 34 | ReturnCode = 0 35 | } 36 | 37 | xRemoteFile dlGit 38 | { 39 | Uri = 'https://github.com/git-for-windows/git/releases/download/v2.9.0.windows.1/Git-2.9.0-64-bit.exe' 40 | DestinationPath = "$($env:Temp)\$($gitFile)" 41 | MatchSource = $false 42 | } 43 | 44 | Package git 45 | { 46 | Ensure = $Ensure 47 | Path = "$($env:Temp)\$($gitFile)" 48 | Name = "Git version 2.9.0" 49 | ProductId = "" 50 | Arguments = '/VERYSILENT /NORESTART /NOCANCEL /SP- /COMPONENTS="icons,icons\quicklaunch,ext,ext\shellhere,ext\guihere,assoc,assoc_sh" /LOG' 51 | DependsOn = '[xRemoteFile]dlGit' 52 | } 53 | 54 | xRemoteFile dlnssm 55 | { 56 | Uri = 'https://nssm.cc/release/nssm-2.24.zip' 57 | DestinationPath = "$($env:Temp)\$($nssmFile)" 58 | MatchSource = $false 59 | } 60 | 61 | Archive nssm 62 | { 63 | Ensure = $Ensure 64 | Path = "$($env:Temp)\$($nssmFile)" 65 | Destination = "C:\nssm" 66 | DependsOn = '[xRemoteFile]dlnssm' 67 | } 68 | } -------------------------------------------------------------------------------- /Examples/HubotInstallService_Example.ps1: -------------------------------------------------------------------------------- 1 | # See dsc_configuration.ps1 -------------------------------------------------------------------------------- /Examples/HubotInstall_Example.ps1: -------------------------------------------------------------------------------- 1 | # See dsc_configuration.ps1 -------------------------------------------------------------------------------- /Examples/dsc_configuration.ps1: -------------------------------------------------------------------------------- 1 | configuration Hubot 2 | { 3 | Import-DscResource -ModuleName PSDesiredStateConfiguration 4 | Import-DscResource -Name MSFT_xRemoteFile -ModuleName xPSDesiredStateConfiguration 5 | Import-DscResource -ModuleName @{ModuleName="Hubot"; RequiredVersion="1.1.5"} 6 | 7 | node $AllNodes.Where{$_.Role -eq "Hubot"}.NodeName 8 | { 9 | # Set an adapter for hubot to use 10 | Environment hubotadapter 11 | { 12 | Name = 'HUBOT_ADAPTER' 13 | Value = $Node.HubotAdapter 14 | Ensure = 'Present' 15 | } 16 | 17 | # Set the hubot debug level - either debug or info 18 | Environment hubotdebug 19 | { 20 | Name = 'HUBOT_LOG_LEVEL' 21 | Value = 'debug' 22 | Ensure = 'Present' 23 | } 24 | 25 | # Set any other environment variables that may be required for the hubot scripts 26 | Environment hubotslacktoken 27 | { 28 | Name = 'HUBOT_SLACK_TOKEN' 29 | Value = $Node.SlackAPIKey 30 | Ensure = 'Present' 31 | } 32 | 33 | # Install the Prereqs using the same Hubot user 34 | HubotPrerequisites installPreqs 35 | { 36 | Ensure = 'Present' 37 | } 38 | 39 | # Download the HubotWindows Repo 40 | xRemoteFile hubotRepo 41 | { 42 | DestinationPath = "$($env:Temp)\HubotWindows.zip" 43 | Uri = "https://github.com/MattHodge/HubotWindows/releases/download/0.0.2/HubotWindows-0.0.2.zip" 44 | } 45 | 46 | # Extract the Hubot Repo 47 | Archive extractHubotRepo 48 | { 49 | Path = "$($env:Temp)\HubotWindows.zip" 50 | Destination = $Node.HubotBotPath 51 | Ensure = 'Present' 52 | DependsOn = '[xRemoteFile]hubotRepo' 53 | } 54 | 55 | # Install Hubot 56 | HubotInstall installHubot 57 | { 58 | BotPath = $Node.HubotBotPath 59 | Ensure = 'Present' 60 | DependsOn = '[Archive]extractHubotRepo','[HubotPrerequisites]installPreqs' 61 | } 62 | 63 | # Install Hubot as a service using NSSM 64 | HubotInstallService myhubotservice 65 | { 66 | BotPath = $Node.HubotBotPath 67 | ServiceName = "Hubot_$($Node.HubotBotName)" 68 | BotAdapter = $Node.HubotAdapter 69 | Ensure = 'Present' 70 | DependsOn = '[HubotInstall]installHubot','[HubotPrerequisites]installPreqs' 71 | } 72 | } 73 | } 74 | 75 | # Configuration Data 76 | $configData = @{ 77 | AllNodes = @( 78 | @{ 79 | NodeName = 'localhost'; 80 | Role = 'Hubot' 81 | SlackAPIKey = 'xoxb-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXX' 82 | HubotAdapter = 'slack' 83 | HubotBotName = 'bender' 84 | HubotBotPath = 'C:\myhubot' 85 | } 86 | ) 87 | } -------------------------------------------------------------------------------- /Hubot.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattHodge/Hubot-DSC-Resource/6267c9fa6b5e724e734fbbb05c5bba4c0c2d67c2/Hubot.psd1 -------------------------------------------------------------------------------- /Hubot.psm1: -------------------------------------------------------------------------------- 1 | # Defines the values for the resource's Ensure property. 2 | enum Ensure 3 | { 4 | # The resource must be absent. 5 | Absent 6 | # The resource must be present. 7 | Present 8 | } 9 | 10 | class HubotHelpers 11 | { 12 | [string] RefreshPathVariable () 13 | { 14 | $updatedPath = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") 15 | return $updatedPath 16 | } 17 | 18 | [bool] CheckPathExists ([string]$Path) 19 | { 20 | if (Test-Path -Path $Path) 21 | { 22 | Write-Verbose "Directory $($Path) exists." 23 | return $true 24 | } 25 | else 26 | { 27 | Write-Verbose "Directory $($Path) exists." 28 | return $false 29 | } 30 | } 31 | 32 | [PSCustomObject] RunProcess([string]$FilePath, [string]$ArgumentList, [string]$WorkingDirectory) 33 | { 34 | $env:Path = [HubotHelpers]::new().RefreshPathVariable() 35 | 36 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo 37 | 38 | if (-not([string]::IsNullOrEmpty($WorkingDirectory))) 39 | { 40 | $pinfo.WorkingDirectory = $WorkingDirectory 41 | } 42 | 43 | $pinfo.FileName = $FilePath 44 | $pinfo.RedirectStandardError = $true 45 | $pinfo.RedirectStandardOutput = $true 46 | $pinfo.UseShellExecute = $false 47 | $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Unicode 48 | $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Unicode 49 | $pinfo.Arguments = $ArgumentList 50 | $p = New-Object System.Diagnostics.Process 51 | $p.StartInfo = $pinfo 52 | $p.Start() | Out-Null 53 | $p.WaitForExit() 54 | $stdout = $p.StandardOutput.ReadToEnd() 55 | $stderr = $p.StandardError.ReadToEnd() 56 | 57 | $output = @{} 58 | $output.filepath = $FilePath 59 | $output.arg = $ArgumentList 60 | $output.workingdirectory = $WorkingDirectory 61 | $output.stdout = $stdout 62 | $output.stderr = $stderr 63 | $output.exitcode = $p.ExitCode 64 | 65 | $returnObj = New-Object -Property $output -TypeName PSCustomObject 66 | Write-Verbose $returnObj.stdout 67 | return $returnObj 68 | } 69 | 70 | [string] GetNSSMPath () 71 | { 72 | if (Test-Path -Path 'C:\nssm') 73 | { 74 | # get latest version installed 75 | $path = ((Get-ChildItem -Path C:\nssm\*\win64\nssm.exe)[-1]).FullName 76 | Write-Verbose "Found nssm at $($path)" 77 | return $path 78 | } 79 | else 80 | { 81 | throw 'NSSM folder cannot be found at C:\nssm' 82 | } 83 | } 84 | } 85 | 86 | [DscResource()] 87 | class HubotInstall 88 | { 89 | 90 | # A DSC resource must define at least one key property. 91 | [DscProperty(Key)] 92 | [string]$BotPath 93 | 94 | [DscProperty(Mandatory)] 95 | [Ensure]$Ensure 96 | 97 | [DscProperty(NotConfigurable)] 98 | [String]$NodeModulesPath 99 | 100 | # Gets the resource's current state. 101 | [HubotInstall] Get() 102 | { 103 | $GetObject = [HubotInstall]::new() 104 | $GetObject.BotPath = $this.BotPath 105 | $GetObject.Ensure = $this.Ensure 106 | $GetObject.NodeModulesPath = Join-Path -Path $this.BotPath -ChildPath 'node_modules' 107 | 108 | 109 | if([HubotHelpers]::new().CheckPathExists($GetObject.NodeModulesPath)) 110 | { 111 | $GetObject.Ensure = [Ensure]::Present 112 | } 113 | else 114 | { 115 | $GetObject.Ensure = [Ensure]::Absent 116 | } 117 | 118 | 119 | return $GetObject 120 | } 121 | 122 | # Sets the desired state of the resource. 123 | [void] Set() 124 | { 125 | $Helpers = [HubotHelpers]::new() 126 | 127 | $env:Path = $Helpers.RefreshPathVariable() 128 | 129 | if (!($Helpers.CheckPathExists($this.BotPath))) 130 | { 131 | 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" 132 | } 133 | 134 | if (Get-Command -CommandType Application -Name npm -ErrorAction SilentlyContinue) 135 | { 136 | $npmPath = (Get-Command -CommandType Application -Name npm)[0].Source 137 | } 138 | else 139 | { 140 | throw "npm cannot be found. Cannot continue." 141 | } 142 | 143 | 144 | if ($this.Ensure -eq [Ensure]::Present) 145 | { 146 | $npmCmd = 'install' 147 | } 148 | else 149 | { 150 | $npmCmd = 'uninstall' 151 | } 152 | 153 | Write-Verbose -Message "$($npmCmd)ing CoffeeScript at $($this.BotPath)" 154 | 155 | Start-Process -FilePath $npmPath -ArgumentList "$($npmCmd) coffee-script" -Wait 156 | 157 | Write-Verbose "$($npmCmd)ing all required npm modules" 158 | 159 | Start-Process -FilePath $npmPath -ArgumentList $npmCmd -Wait 160 | 161 | if ($this.Ensure -eq [Ensure]::Absent) 162 | { 163 | Remove-Item -Path $this.NodeModulesPath -Force 164 | } 165 | } 166 | 167 | # Tests if the resource is in the desired state. 168 | [bool] Test() 169 | { 170 | $TestObject = $This.Get() 171 | 172 | # present case 173 | if ($TestObject.Ensure -eq [Ensure]::Present) 174 | { 175 | return $true 176 | } 177 | # absent case 178 | else 179 | { 180 | return $false 181 | } 182 | } 183 | } 184 | 185 | [DscResource()] 186 | class HubotInstallService 187 | { 188 | 189 | # Path where the Hubot is located 190 | [DscProperty(Key)] 191 | [string]$BotPath 192 | 193 | # Name for the Hubot service 194 | [DscProperty(Mandatory)] 195 | [string]$ServiceName 196 | 197 | # Credential to run the service under 198 | [DscProperty()] 199 | [PSCredential]$Credential 200 | 201 | # Bot adapter for Hubot to be used. Used as a paramater to start the server (-a $botadapter) 202 | [DscProperty(Mandatory)] 203 | [string]$BotAdapter 204 | 205 | [DscProperty(Mandatory)] 206 | [Ensure]$Ensure 207 | 208 | [DscProperty(NotConfigurable)] 209 | [Boolean]$State_ServiceExists 210 | 211 | [DscProperty(NotConfigurable)] 212 | [Boolean]$State_ServiceRunning 213 | 214 | [DscProperty(NotConfigurable)] 215 | [string]$NSSMAppParameters 216 | 217 | [DscProperty(NotConfigurable)] 218 | [string[]]$NSSMCmdsToRun 219 | 220 | [DscProperty(NotConfigurable)] 221 | [string]$BotLoggingPath 222 | 223 | [DscProperty(NotConfigurable)] 224 | [Boolean]$State_NSSMAppParameters 225 | 226 | # Gets the resource's current state. 227 | [HubotInstallService] Get() 228 | { 229 | $Helpers = [HubotHelpers]::new() 230 | 231 | $GetObject = [HubotInstallService]::new() 232 | $GetObject.BotPath = $this.BotPath 233 | $GetObject.Ensure = $this.Ensure 234 | $GetObject.ServiceName = $this.ServiceName 235 | $GetObject.Credential = $this.Credential 236 | $GetObject.BotAdapter = $this.BotAdapter 237 | $GetObject.NSSMAppParameters = "/c .\bin\hubot.cmd -a $($this.BotAdapter)" 238 | 239 | # set default states to save having nested if statements 240 | $GetObject.State_ServiceExists = $false 241 | $GetObject.State_ServiceRunning = $false 242 | $GetObject.State_NSSMAppParameters = $false 243 | 244 | if (Get-Service -Name $this.ServiceName -ErrorAction SilentlyContinue) 245 | { 246 | $GetObject.State_ServiceExists = $true 247 | 248 | # Check service is running 249 | if ((Get-Service -Name $this.ServiceName).Status -eq 'Running') 250 | { 251 | $GetObject.State_ServiceRunning = $true 252 | } 253 | 254 | $nssmPath = $Helpers.GetNSSMPath() 255 | 256 | # check if appparams set correctly 257 | $currentAppParams = ($Helpers.RunProcess($nssmPath,"get $($this.ServiceName) AppParameters",$null)).stdout 258 | 259 | # need to use trim to remove white spaces 260 | if ([string]$currentAppParams.Trim() -eq [string]$GetObject.NSSMAppParameters) 261 | { 262 | $GetObject.State_NSSMAppParameters = $true 263 | } 264 | } 265 | 266 | # get the bot logging path 267 | $GetObject.BotLoggingPath = Join-Path -Path $GetObject.BotPath -ChildPath 'Logs' 268 | 269 | # build an array of NSSM Commands to execute based upon user input 270 | $GetObject.NSSMCmdsToRun = @( 271 | "install $($this.ServiceName) cmd.exe" 272 | "set $($this.ServiceName) AppDirectory $($GetObject.BotPath)" 273 | "set $($this.ServiceName) AppParameters ""/c .\bin\hubot.cmd -a $($GetObject.BotAdapter)""" 274 | "set $($this.ServiceName) AppStdout ""$($GetObject.BotLoggingPath)\$($GetObject.ServiceName)_log.txt""" 275 | "set $($this.ServiceName) AppStderr ""$($GetObject.BotLoggingPath)\$($GetObject.ServiceName)_error.txt""" 276 | "set $($this.ServiceName) AppDirectory $($GetObject.BotPath)" 277 | "set $($this.ServiceName) AppRotateFiles 1" 278 | "set $($this.ServiceName) AppRotateOnline 1" 279 | "set $($this.ServiceName) AppRotateSeconds 86400" 280 | "set $($this.ServiceName) Description Hubot Service" 281 | "set $($this.ServiceName) Start SERVICE_AUTO_START" 282 | ) 283 | 284 | # if a credetial is passed with no password assume LocalSystem 285 | if ([string]::IsNullOrEmpty($GetObject.Credential)) 286 | { 287 | Write-Verbose "No credential passed, using LocalSystem." 288 | $GetObject.NSSMCmdsToRun += "set $($GetObject.ServiceName) ObjectName LocalSystem" 289 | } 290 | # if a credential is passed with a password 291 | else 292 | { 293 | Write-Verbose "Credential passed, using username $($GetObject.Credential.UserName)." 294 | $GetObject.NSSMCmdsToRun += "set $($GetObject.ServiceName) ObjectName .\$($GetObject.Credential.UserName) $($GetObject.Credential.GetNetworkCredential().Password)" 295 | } 296 | 297 | return $GetObject 298 | } 299 | 300 | [void] Set() 301 | { 302 | $Helpers = [HubotHelpers]::new() 303 | 304 | $env:Path = $Helpers.RefreshPathVariable() 305 | 306 | $TestObject = $This.Get() 307 | 308 | $nssmPath = $Helpers.GetNSSMPath() 309 | 310 | if ($this.Ensure -eq [Ensure]::Present) 311 | { 312 | 313 | if ($TestObject.State_ServiceExists) 314 | { 315 | Write-Verbose "Removing old service" 316 | Stop-Service -Name $this.ServiceName -Force 317 | $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null 318 | } 319 | 320 | # Create bot logging path 321 | Write-Verbose "Creating bot logging path at $($TestObject.BotLoggingPath)" 322 | New-Item -Path $TestObject.BotLoggingPath -Force -ItemType Directory 323 | 324 | ForEach ($cmd in $TestObject.NSSMCmdsToRun) 325 | { 326 | $Helpers.RunProcess($nssmPath,$cmd,$null) | Out-Null 327 | } 328 | 329 | Start-Service -Name $this.ServiceName 330 | } 331 | else 332 | { 333 | Write-Verbose "Removing Bot Service $($this.ServiceName)" 334 | Stop-Service -Name $this.ServiceName -Force -ErrorAction SilentlyContinue 335 | $Helpers.RunProcess($nssmPath,"remove $($this.ServiceName) confirm",$null) | Out-Null 336 | } 337 | } 338 | 339 | # Tests if the resource is in the desired state. 340 | [bool] Test() 341 | { 342 | $TestObject = $This.Get() 343 | 344 | # present case 345 | if ($this.Ensure -eq [Ensure]::Present) 346 | { 347 | # If any of the possible states for the service are false, not in desired state 348 | return (-not($TestObject.psobject.Properties.Where({$PSItem.Name -like 'State_*'}).Value -contains $false)) 349 | } 350 | # absent case 351 | else 352 | { 353 | return (-not($TestObject.State_ServiceExists)) 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matthew Hodgkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hubot (DSC Resource) 2 | 3 | ![Hubot](http://i.imgur.com/NhTqeZ2.png) 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/kjweb2q53xa3198h?svg=true)](https://ci.appveyor.com/project/MattHodge/hubot-dsc-resource) 6 | 7 | The **Hubot** module contains the `HubotPrerequisites`, `HubotInstall` and `HubotInstallService` DSC Resources to install Hubot on Windows. 8 | 9 | This resource installs and runs Hubot as a service on Windows using NSSM. 10 | 11 | I recommend using the [HubotWindows](https://github.com/MattHodge/HubotWindows) repository to get the Hubot setup on your node and use the following DSC resources to configure it. 12 | 13 | For an introduction to using Hubot on Windows, take a look at [ChatOps on Windows with Hubot and PowerShell](https://hodgkins.io/chatops-on-windows-with-hubot-and-powershell). 14 | 15 | ## Resources 16 | 17 | ### HubotPrerequisites 18 | 19 | Parameter | Notes | Mandatory 20 | | --- | --- | --- | 21 | Ensure | Ensures that the prerequisites is Present or Absent | `Yes` 22 | 23 | ### HubotInstall 24 | 25 | Parameter | Notes | Mandatory 26 | | --- | --- | --- | 27 | BotPath | Path that the Windows Hubot package is installed. (Get from here: https://github.com/MattHodge/HubotWindows) | `Yes` 28 | Ensure | Ensures that the bot is Present or Absent | `Yes` 29 | 30 | ### HubotInstallService 31 | 32 | Parameter | Notes | Mandatory 33 | | --- | --- | --- | 34 | BotPath | Path that the Windows Hubot package is installed. (Get from here: https://github.com/MattHodge/HubotWindows) | `Yes` 35 | ServiceName | Name to give the Hubot Windows service | `Yes` 36 | Credential | Credential of account to run the Hubot service under. If left blank, the service will run under the `SYSTEM` account. | `No` 37 | BotAdapter | The name of the Hubot adapter to use. (https://github.com/github/hubot/blob/master/docs/adapters.md) | `Yes` 38 | Ensure | Ensures that the bot is Present or Absent | `Yes` 39 | 40 | ## Examples 41 | 42 | You can find an installation example here: [dsc_configuration.ps1](Examples/dsc_configuration.ps1) 43 | 44 | ## Installation 45 | 46 | To install the module, use: 47 | 48 | `Install-Module -Name Hubot` 49 | 50 | A video of the installation on a remote machine: 51 | 52 | Hubot DSC Installation Video 55 | 56 | ## Packaging 57 | 58 | The DSC Resource Module is called `Hubot` and is available on the PowerShell Gallery: 59 | * https://www.powershellgallery.com/packages/Hubot 60 | 61 | ## Testing Using Test-Kitchen 62 | * Make sure the repo is cloned as `Hubot` or Test-Kitchen will not work. 63 | 64 | ## Versions 65 | 66 | ### 1.1.5 67 | 68 | * Updated module dependencies so it pulls down `xPSDesiredStateConfiguration` on install. 69 | 70 | ### 1.1.4 71 | 72 | * Removing dependency on `cChoco` and `Chocolatey`. This requires the node to reboot after installing Node.js as part of the `HubotPrerequisites` resource unfortunately. 73 | 74 | 75 | ### 1.1.3 76 | 77 | * Initial Release 78 | -------------------------------------------------------------------------------- /Tests/HubotHelpers_Unit_Tests.ps1: -------------------------------------------------------------------------------- 1 | ################# 2 | # TEST HEADER # 3 | ################# 4 | 5 | $dscModule = 'Hubot' 6 | 7 | $originalLocation = Get-Location 8 | 9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 10 | Set-Location $here 11 | 12 | describe "HubotHelpers" { 13 | 14 | ###################################### 15 | # USED FOR TESTING CLASS BASED DSC # 16 | ###################################### 17 | 18 | # To support $root\Tests folder structure 19 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'} 20 | # To support $root\Tests\Unit folder structure 21 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'} 22 | # Or simply throw... 23 | Else {Throw 'Unable to find source .psm1 file to test against.'} 24 | 25 | # Dot source the file to brint classes into current session 26 | . 'TestDrive:\script.ps1' 27 | 28 | ################ 29 | # TEST START # 30 | ################ 31 | 32 | $guid = (New-Guid).Guid 33 | 34 | context "RefreshPathVariable" { 35 | it "returns something" { 36 | [HubotHelpers]::new().RefreshPathVariable() | Should Not Be Null 37 | } 38 | 39 | it "return should contain atleast one semicolon" { 40 | [HubotHelpers]::new().RefreshPathVariable() | Should BeLike "*;*" 41 | } 42 | 43 | it "when split into array should have 2 or more items" { 44 | $array = [HubotHelpers]::new().RefreshPathVariable() -split ';' 45 | $array.Count | Should BeGreaterThan 1 46 | } 47 | } 48 | 49 | context "CheckPathExists" { 50 | it "returns true when path that exists is passed" { 51 | [HubotHelpers]::new().CheckPathExists("TestDrive:\") | Should Be $true 52 | } 53 | it "returns false when path that does not exist is passed" { 54 | [HubotHelpers]::new().CheckPathExists("TestDrive:\$($guid)") | Should Be $false 55 | } 56 | } 57 | } 58 | 59 | Set-Location $originalLocation -------------------------------------------------------------------------------- /Tests/HubotInstallService_Unit_Tests.ps1: -------------------------------------------------------------------------------- 1 | ################# 2 | # TEST HEADER # 3 | ################# 4 | 5 | $dscModule = 'Hubot' 6 | 7 | $originalLocation = Get-Location 8 | 9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 10 | Set-Location $here 11 | 12 | 13 | describe "HubotInstallService" { 14 | 15 | ###################################### 16 | # USED FOR TESTING CLASS BASED DSC # 17 | ###################################### 18 | 19 | # To support $root\Tests folder structure 20 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'} 21 | # To support $root\Tests\Unit folder structure 22 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'} 23 | # Or simply throw... 24 | Else {Throw 'Unable to find source .psm1 file to test against.'} 25 | 26 | # Dot source the file to brint classes into current session 27 | . 'TestDrive:\script.ps1' 28 | 29 | ################ 30 | # TEST START # 31 | ################ 32 | 33 | 34 | context "Get" { 35 | BeforeEach { 36 | $TestClass = [HubotInstallService]::new() 37 | 38 | # Generate credentials to use 39 | $password = ConvertTo-SecureString -String 'vagrant' -AsPlainText -Force 40 | $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'vagrant', $password 41 | } 42 | 43 | it "does not throw" { 44 | { 45 | $TestClass.BotPath = 'C:\myhubot' 46 | $TestClass.ServiceName = 'hubot_service' 47 | $TestClass.BotAdapter = 'slack' 48 | $TestClass.Ensure = 'Present' 49 | $TestClass.Get() 50 | } | Should Not Throw 51 | } 52 | 53 | it "nssm cmds are propegated" { 54 | 55 | $TestClass = [HubotInstallService]::new() 56 | $TestClass.BotPath = 'C:\myhubot' 57 | $TestClass.ServiceName = 'hubot_service' 58 | $TestClass.BotAdapter = 'slack' 59 | $TestClass.Ensure = 'Present' 60 | $TestClass.Get().NSSMCmdsToRun | Should Not BeNullOrEmpty 61 | } 62 | 63 | it "hubot generates the correct logging path under the botpath" { 64 | 65 | $TestClass = [HubotInstallService]::new() 66 | $TestClass.BotPath = 'C:\myhubot' 67 | $TestClass.ServiceName = 'hubot_service' 68 | $TestClass.BotAdapter = 'slack' 69 | $TestClass.Ensure = 'Present' 70 | $TestClass.Get().BotLoggingPath | Should BeExactly 'C:\myhubot\Logs' 71 | } 72 | 73 | it "nssm uses the correct log path when creating service" { 74 | 75 | $TestClass = [HubotInstallService]::new() 76 | $TestClass.BotPath = 'C:\myhubot' 77 | $TestClass.ServiceName = 'hubot_service' 78 | $TestClass.BotAdapter = 'slack' 79 | $TestClass.Ensure = 'Present' 80 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service AppStdout `"c:\myhubot\Logs\hubot_service_log.txt`"" | Should Be $true 81 | } 82 | 83 | it "nssm uses the correct errorlog path when creating service" { 84 | 85 | $TestClass = [HubotInstallService]::new() 86 | $TestClass.BotPath = 'C:\myhubot' 87 | $TestClass.ServiceName = 'hubot_service' 88 | $TestClass.BotAdapter = 'slack' 89 | $TestClass.Ensure = 'Present' 90 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service AppStderr `"c:\myhubot\Logs\hubot_service_error.txt`"" | Should Be $true 91 | } 92 | 93 | it "nssm uses localsystem to create service when no credential passed" { 94 | 95 | $TestClass = [HubotInstallService]::new() 96 | $TestClass.BotPath = 'C:\myhubot' 97 | $TestClass.ServiceName = 'hubot_service' 98 | $TestClass.BotAdapter = 'slack' 99 | $TestClass.Ensure = 'Present' 100 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service ObjectName LocalSystem" | Should Be $true 101 | } 102 | 103 | it "nssm uses credentials to create service when a is credential passed" { 104 | 105 | $TestClass = [HubotInstallService]::new() 106 | $TestClass.BotPath = 'C:\myhubot' 107 | $TestClass.ServiceName = 'hubot_service' 108 | $TestClass.BotAdapter = 'slack' 109 | $TestClass.Ensure = 'Present' 110 | $TestClass.Credential = $cred 111 | $TestClass.Get().NSSMCmdsToRun -contains "set hubot_service ObjectName .\$($cred.UserName) $($cred.GetNetworkCredential().Password)" | Should Be $true 112 | } 113 | 114 | it "nsssm uses the user provided hubot adapater" { 115 | 116 | $TestClass = [HubotInstallService]::new() 117 | $TestClass.BotPath = 'C:\myhubot' 118 | $TestClass.ServiceName = 'hubot_service' 119 | $TestClass.BotAdapter = 'slack' 120 | $TestClass.Ensure = 'Present' 121 | $TestClass.Get().NSSMAppParameters | Should Be '/c .\bin\hubot.cmd -a slack' 122 | } 123 | } 124 | } 125 | 126 | Set-Location $originalLocation -------------------------------------------------------------------------------- /Tests/HubotInstall_Unit_Tests.ps1: -------------------------------------------------------------------------------- 1 | ################# 2 | # TEST HEADER # 3 | ################# 4 | 5 | $dscModule = 'Hubot' 6 | 7 | $originalLocation = Get-Location 8 | 9 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 10 | Set-Location $here 11 | 12 | 13 | describe "HubotInstall" { 14 | 15 | ###################################### 16 | # USED FOR TESTING CLASS BASED DSC # 17 | ###################################### 18 | 19 | # To support $root\Tests folder structure 20 | If (Test-Path "..\$dscModule.psm1") {Copy-Item "..\$dscModule.psm1" 'TestDrive:\script.ps1'} 21 | # To support $root\Tests\Unit folder structure 22 | ElseIf (Test-Path "..\..\$dscModule.psm1") {Copy-Item "..\..\$dscModule.psm1" 'TestDrive:\script.ps1'} 23 | # Or simply throw... 24 | Else {Throw 'Unable to find source .psm1 file to test against.'} 25 | 26 | # Dot source the file to brint classes into current session 27 | . 'TestDrive:\script.ps1' 28 | 29 | ################ 30 | # TEST START # 31 | ################ 32 | 33 | $guid = (New-Guid).Guid 34 | 35 | context "Get" { 36 | it "does not throw" { 37 | { 38 | $x = [HubotInstall]::new() 39 | $x.BotPath = 'C:\myhubot' 40 | $x.Ensure = 'Present' 41 | $x.Get() 42 | } | Should Not Throw 43 | } 44 | 45 | it "returns a [HubotInstall] class" { 46 | $x = [HubotInstall]::new() 47 | $x.BotPath = 'TestDrive:\' 48 | $x.Ensure = 'Present' 49 | $x.Get().GetType().Name | Should Be 'HubotInstall' 50 | } 51 | 52 | it "returns ensure present if BotPath exists" { 53 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 54 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 55 | $x = [HubotInstall]::new() 56 | $x.BotPath = $TestDrive 57 | $x.Get().Ensure | Should Be 'Present' 58 | } 59 | 60 | it "node modules path should be valid" { 61 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 62 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 63 | $x = [HubotInstall]::new() 64 | $x.BotPath = $TestDrive 65 | $x.Get().NodeModulesPath | Should Be $fakeModuleFolder 66 | } 67 | 68 | it "returns ensure absent if BotPath does not exist" { 69 | $x = [HubotInstall]::new() 70 | $x.BotPath = "TestDrive:\$($guid)" 71 | $x.Get().Ensure | Should Be 'Absent' 72 | } 73 | } 74 | 75 | context "Set" { 76 | BeforeEach { 77 | $TestClass = [HubotInstall]::new() 78 | 79 | Mock Start-Process { return $true } 80 | 81 | $getCmdMockObject = @" 82 | 83 | 84 | 85 | System.Management.Automation.ApplicationInfo 86 | System.Management.Automation.CommandInfo 87 | System.Object 88 | 89 | npm.cmd 90 | 91 | C:\Program Files\nodejs\npm.cmd 92 | .cmd 93 | C:\Program Files\nodejs\npm.cmd 94 | C:\Program Files\nodejs\npm.cmd 95 | 0.0.0.0 96 | 97 | 98 | System.Management.Automation.SessionStateEntryVisibility 99 | System.Enum 100 | System.ValueType 101 | System.Object 102 | 103 | Public 104 | 0 105 | 106 | 107 | 108 | System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Management.Automation.PSTypeName, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]] 109 | System.Object 110 | 111 | 112 | System.String 113 | 114 | 115 | npm.cmd 116 | 117 | 118 | System.Management.Automation.CommandTypes 119 | System.Enum 120 | System.ValueType 121 | System.Object 122 | 123 | Application 124 | 32 125 | 126 | 127 | 128 | 129 | 130 | System.Management.Automation.RemotingCapability 131 | System.Enum 132 | System.ValueType 133 | System.Object 134 | 135 | PowerShell 136 | 1 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | System.Diagnostics.FileVersionInfo 145 | System.Object 146 | 147 | File: C:\Program Files\nodejs\npm.cmd_x000D__x000A_InternalName: _x000D__x000A_OriginalFilename: _x000D__x000A_FileVersion: _x000D__x000A_FileDescription: _x000D__x000A_Product: _x000D__x000A_ProductVersion: _x000D__x000A_Debug: False_x000D__x000A_Patched: False_x000D__x000A_PreRelease: False_x000D__x000A_PrivateBuild: False_x000D__x000A_SpecialBuild: False_x000D__x000A_Language: _x000D__x000A_ 148 | 149 | 150 | 151 | 0 152 | 153 | 0 154 | 0 155 | C:\Program Files\nodejs\npm.cmd 156 | 0 157 | 158 | 159 | false 160 | false 161 | false 162 | false 163 | false 164 | 165 | 166 | 167 | 168 | 169 | 0 170 | 0 171 | 0 172 | 173 | 0 174 | 175 | 176 | 177 | 178 | 0.0.0.0 179 | 0.0.0.0 180 | 181 | 182 | 183 | 184 | 185 | "@ 186 | 187 | $myobj = [System.Management.Automation.PSSerializer]::Deserialize($getCmdMockObject) 188 | 189 | Mock Get-Command { return $myobj } 190 | 191 | } 192 | 193 | it "throws when botpath is not found" { 194 | { 195 | $TestClass.BotPath = "$TestDrive\$($guid)" 196 | $result = $TestClass.Get() 197 | $result.Set() 198 | } | Should Throw 199 | } 200 | 201 | it "does not throw when botpath is found and ensure is present" { 202 | { 203 | $TestClass.BotPath = "$TestDrive" 204 | $result = $TestClass.Get() 205 | $result.Ensure = 'Present' 206 | $result.Set() 207 | } | Should Not Throw 208 | } 209 | 210 | it "throws when npm not found" { 211 | Mock Get-Command { return $null } 212 | 213 | { 214 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 215 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 216 | $TestClass.BotPath = $TestDrive 217 | $result = $TestClass.Get() 218 | $result.Set() 219 | } | Should Throw 220 | } 221 | 222 | it "does not throw when npm not found" { 223 | { 224 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 225 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 226 | $TestClass.BotPath = $TestDrive 227 | $result = $TestClass.Get() 228 | $result.Set() 229 | } | Should Not Throw 230 | } 231 | 232 | it "calls start-process twice to uninstall coffee-script and then npm" { 233 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 234 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 235 | $TestClass.BotPath = $TestDrive 236 | $result = $TestClass.Get() 237 | $result.Ensure = 'Absent' 238 | $result.Set() 239 | Assert-MockCalled Start-Process 2 -Exactly -ParameterFilter {$FilePath -like "*npm*" -and $ArgumentList -like "*uninstall*"} -Scope It 240 | } 241 | 242 | 243 | it "calls start-process twice to install coffee-script and then npm install" { 244 | $fakeModuleFolder = Join-Path $TestDrive 'node_modules' 245 | New-Item -Path $fakeModuleFolder -ItemType Directory -Force 246 | $TestClass.BotPath = $TestDrive 247 | $result = $TestClass.Get() 248 | $result.Set() 249 | Assert-MockCalled Start-Process 2 -Exactly -ParameterFilter {$FilePath -like "*npm*" -and $ArgumentList -like "*install*"} -Scope It 250 | } 251 | } 252 | } 253 | 254 | Set-Location $originalLocation -------------------------------------------------------------------------------- /Tests/Hubot_MOF_Generation_Tests.ps1: -------------------------------------------------------------------------------- 1 | describe "Hubot DSC Module - MOF Testing" { 2 | 3 | $dscExamplePath = Join-path -Path '.\Examples' -ChildPath 'dsc_configuration.ps1' 4 | 5 | context "Get-DSCResource" { 6 | $res = Get-DscResource 7 | 8 | it "returns something" { 9 | $res | Should Not Be Null 10 | } 11 | 12 | $hubotRes = @( 13 | 'HubotInstall' 14 | 'HubotInstallService' 15 | 'HubotPrerequisites' 16 | ) 17 | 18 | ForEach ($h in $hubotRes) 19 | { 20 | it "contains resource $($h)" { 21 | $res.Name -contains $h | Should Be $true 22 | } 23 | } 24 | } 25 | 26 | context "Example dsc_configuration" { 27 | it "is valid powershell" { 28 | $psfile = Get-Content -Path $dscExamplePath -Raw -ErrorAction Stop 29 | $errors = $null 30 | $null = [System.Management.Automation.PSParser]::Tokenize($psfile, [ref]$errors) 31 | $errors.Count | Should Be 0 32 | } 33 | 34 | . $dscExamplePath 35 | 36 | it "module version of Hubot.psd1 matches module version in $dscExamplePath" { 37 | $moduleVersion = Select-String -Path .\Hubot.psd1 -Pattern "ModuleVersion = '(.*)'" 38 | $moduleVersion = $moduleVersion.Matches.Groups[1].Value 39 | 40 | $exampleVersion = Select-String -Path $dscExamplePath -Pattern 'ModuleName=\"Hubot\"\; RequiredVersion=\"(.*)\"' 41 | $exampleVersion = $exampleVersion.Matches.Groups[1].Value 42 | 43 | $exampleVersion | Should BeExactly $moduleVersion 44 | } 45 | 46 | it "does not have a real api key" { 47 | $configData.AllNodes.SlackAPIKey | Should Be 'xoxb-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXX' 48 | } 49 | 50 | it "does not throw on mof generation" { 51 | { Hubot -OutputPath TestDrive:\mof -ConfigurationData $configData } | Should Not Throw 52 | } 53 | 54 | it "mof file is created on disk" { 55 | Test-Path -Path "TestDrive:\mof\localhost.mof" | Should Be $true 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/appveyor.pester.ps1: -------------------------------------------------------------------------------- 1 | # This script will invoke pester tests 2 | # It should invoke on PowerShell v2 and later 3 | # We serialize XML results and pull them in appveyor.yml 4 | 5 | #If Finalize is specified, we collect XML output, upload tests, and indicate build errors 6 | param( 7 | [switch]$Finalize, 8 | [switch]$Test, 9 | [string]$ProjectRoot = $ENV:APPVEYOR_BUILD_FOLDER, 10 | [string]$TestPath = "$ProjectRoot\Tests" 11 | ) 12 | 13 | #Initialize some variables, move to the project root 14 | $Timestamp = Get-date -uformat "%Y%m%d-%H%M%S" 15 | $PSVersion = $PSVersionTable.PSVersion.Major 16 | $TestFile = "TestResults_PS$PSVersion`_$TimeStamp.xml" 17 | 18 | $Address = "https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)" 19 | Set-Location $ProjectRoot 20 | 21 | $Verbose = @{} 22 | if($env:APPVEYOR_REPO_BRANCH -and $env:APPVEYOR_REPO_BRANCH -notlike "master") 23 | { 24 | $Verbose.add("Verbose",$True) 25 | } 26 | 27 | #Run a test with the current version of PowerShell, upload results 28 | if($Test) 29 | { 30 | "`n`tSTATUS: Testing with PowerShell $PSVersion`n" 31 | 32 | Import-Module Pester 33 | 34 | if ($env:APPVEYOR) 35 | { 36 | Invoke-Pester @Verbose -Path $TestPath -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -PassThru | 37 | Export-Clixml -Path "$ProjectRoot\PesterResults_PS$PSVersion`_$Timestamp.xml" 38 | } 39 | else 40 | { 41 | Invoke-Pester @Verbose -Path $TestPath 42 | } 43 | 44 | 45 | If($env:APPVEYOR_JOB_ID) 46 | { 47 | (New-Object 'System.Net.WebClient').UploadFile( $Address, "$ProjectRoot\$TestFile" ) 48 | } 49 | } 50 | 51 | #If finalize is specified, display errors and fail build if we ran into any 52 | If($Finalize) 53 | { 54 | #Show status... 55 | $AllFiles = Get-ChildItem -Path $ProjectRoot\PesterResults*.xml | Select -ExpandProperty FullName 56 | "`n`tSTATUS: Finalizing results`n" 57 | "COLLATING FILES:`n$($AllFiles | Out-String)" 58 | 59 | #What failed? 60 | $Results = @( Get-ChildItem -Path "$ProjectRoot\PesterResults_PS*.xml" | Import-Clixml ) 61 | 62 | $FailedCount = $Results | 63 | Select -ExpandProperty FailedCount | 64 | Measure-Object -Sum | 65 | Select -ExpandProperty Sum 66 | 67 | if ($FailedCount -gt 0) { 68 | 69 | $FailedItems = $Results | 70 | Select -ExpandProperty TestResult | 71 | Where {$_.Passed -notlike $True} 72 | 73 | "FAILED TESTS SUMMARY:`n" 74 | $FailedItems | ForEach-Object { 75 | $Item = $_ 76 | [pscustomobject]@{ 77 | Describe = $Item.Describe 78 | Context = $Item.Context 79 | Name = "It $($Item.Name)" 80 | Result = $Item.Result 81 | } 82 | } | 83 | Sort Describe, Context, Name, Result | 84 | Format-List 85 | 86 | throw "$FailedCount tests failed." 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/integration/default/pester/default.tests.ps1: -------------------------------------------------------------------------------- 1 | describe "Hubot" { 2 | 3 | Context "File Paths and Installs" { 4 | $pathsToTest = @( 5 | 'C:\Program Files\Git\bin\git.exe' 6 | 'C:\Program Files\nodejs\node.exe' 7 | 'C:\nssm' 8 | 'C:\myhubot' 9 | 'C:\myhubot\node_modules' 10 | 'C:\myhubot\node_modules\edge' 11 | 'C:\myhubot\node_modules\edge-ps' 12 | 'C:\myhubot\node_modules\coffee-script' 13 | ) 14 | 15 | ForEach ($p in $pathsToTest) 16 | { 17 | it "$($p) exists" { 18 | Test-Path -Path $p | Should Be $true 19 | } 20 | } 21 | } 22 | 23 | Context "Environment Variables" { 24 | $envVarsToTest = @( 25 | 'HUBOT_ADAPTER' 26 | 'HUBOT_LOG_LEVEL' 27 | 'HUBOT_SLACK_TOKEN' 28 | ) 29 | 30 | ForEach ($e in $envVarsToTest) 31 | { 32 | It "environment variable $($e) should exist" { 33 | Test-Path -Path "Env:\$($e)" | Should Be $true 34 | } 35 | 36 | } 37 | } 38 | 39 | Context "Hubot Service" { 40 | It "should exist" { 41 | { Get-Service -Name Hubot_bender } | Should Not throw 42 | } 43 | It "should be running" { 44 | (Get-Service -Name Hubot_bender).Status | Should BeExactly 'Running' 45 | } 46 | 47 | $svc = Get-WMIObject -Class Win32_Service -Filter "Name='hubot_bender'" | Select-Object * 48 | 49 | It "should have a description of Hubot Service" { 50 | $svc.Description | Should BeExactly 'Hubot Service' 51 | } 52 | 53 | It "should be running under Hubot account" { 54 | $svc.StartName | Should BeExactly 'LocalSystem' 55 | } 56 | 57 | It "should have a startmode of Auto" { 58 | $svc.StartMode | Should BeExactly 'Auto' 59 | } 60 | 61 | It "should create stdout log file at c:\myhubot\Logs\hubot_bender_log.txt" { 62 | 'c:\myhubot\Logs\hubot_bender_log.txt' | Should Exist 63 | } 64 | 65 | It "should create stderr log file at c:\myhubot\Logs\hubot_bender_error.txt" { 66 | 'c:\myhubot\Logs\hubot_bender_error.txt' | Should Exist 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # See http://www.appveyor.com/docs/appveyor-yml for many more options 2 | 3 | # Skip on updates to the readme. 4 | # We can force this by adding [skip ci] or [ci skip] anywhere in commit message 5 | skip_commits: 6 | message: /updated readme.*/ 7 | files: 8 | - README.md 9 | 10 | os: "WMF 5" 11 | 12 | install: 13 | - ps: Write-Output "Build Number $($env:APPVEYOR_BUILD_NUMBER)" 14 | - ps: Write-Output "Build Version $($env:APPVEYOR_BUILD_VERSION)" 15 | 16 | build: false 17 | version: '1.1.{build}' 18 | 19 | before_test: 20 | - ps: .\build.ps1 21 | 22 | test_script: 23 | # Test with native PS version 24 | # - ps: . .\Tests\appveyor.pester.ps1 -Test 25 | # Finalize pass - collect and upload results 26 | - ps: . .\Tests\appveyor.pester.ps1 -Finalize 27 | 28 | branches: 29 | # whitelist 30 | only: 31 | - master 32 | 33 | #on_finish: 34 | #- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 35 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | [cmdletbinding()] 3 | param( 4 | [string[]]$Task = 'default' 5 | ) 6 | 7 | if (!(Get-PackageProvider -Name Nuget -ErrorAction SilentlyContinue)) 8 | { 9 | Install-PackageProvider -Name NuGet -Force 10 | } 11 | 12 | $modulesToInstall = @( 13 | 'Pester', 14 | 'psake', 15 | 'PSDeploy', 16 | 'PSScriptAnalyzer' 17 | 'cChoco' # Required by DSC Resource 18 | 'xPSDesiredStateConfiguration' # Required by DSC Resource 19 | ) 20 | 21 | ForEach ($module in $modulesToInstall) 22 | { 23 | if (!(Get-Module -Name $module -ListAvailable)) 24 | { 25 | Install-Module -Name $module -Force -Scope CurrentUser 26 | } 27 | } 28 | 29 | if (-not($env:APPVEYOR)) 30 | { 31 | $env:appveyor_build_version = '10.10.10' 32 | Write-Verbose "Not on AppVeyor, using fake version of $($env:appveyor_build_version)." 33 | } 34 | 35 | # Invoke PSake 36 | Invoke-psake -buildFile "$PSScriptRoot\psakeBuild.ps1" -taskList $Task -parameters @{'build_version' = $env:appveyor_build_version} -Verbose:$VerbosePreference -------------------------------------------------------------------------------- /psakeBuild.ps1: -------------------------------------------------------------------------------- 1 | properties { 2 | # $unitTests = "$PSScriptRoot\Tests\unit" 3 | $unitTests = Get-ChildItem .\Tests\*Unit_Tests.ps1 4 | $mofTests = Get-ChildItem .\Tests\*MOF_Generation_Tests.ps1 5 | $DSCResources = Get-ChildItem *.psd1,*.psm1 -Recurse 6 | 7 | # originalPath is the one containing the .psm1 and .psd1 8 | $originalPath = $PSScriptRoot 9 | 10 | # pathInModuleDir is the path where the symbolic link will be created which points to your repo 11 | $pathInModuleDir = 'C:\Program Files\WindowsPowerShell\Modules\Hubot' 12 | } 13 | 14 | task default -depends Analyze, Test, MOFTestDeploy, MOFTest, BuildArtifact 15 | 16 | task TestProperties { 17 | Assert ($build_version -ne $null) "build_version should not be null" 18 | } 19 | 20 | task Analyze { 21 | ForEach ($resource in $DSCResources) 22 | { 23 | try 24 | { 25 | Write-Output "Running ScriptAnalyzer on $($resource)" 26 | 27 | if ($env:APPVEYOR) 28 | { 29 | Add-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Running 30 | $timer = [System.Diagnostics.Stopwatch]::StartNew() 31 | } 32 | 33 | $saResults = Invoke-ScriptAnalyzer -Path $resource.FullName -Verbose:$false 34 | if ($saResults) { 35 | $saResults | Format-Table 36 | $saResultsString = $saResults | Out-String 37 | if ($saResults.Severity -contains 'Error' -or $saResults.Severity -contains 'Warning') 38 | { 39 | if ($env:APPVEYOR) 40 | { 41 | Add-AppveyorMessage -Message "PSScriptAnalyzer output contained one or more result(s) with 'Error or Warning' severity.` 42 | Check the 'Tests' tab of this build for more details." -Category Error 43 | Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Failed -ErrorMessage $saResultsString 44 | } 45 | 46 | Write-Error -Message "One or more Script Analyzer errors/warnings where found in $($resource). Build cannot continue!" 47 | } 48 | else 49 | { 50 | Write-Output "All ScriptAnalyzer tests passed" 51 | 52 | if ($env:APPVEYOR) 53 | { 54 | Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Passed -StdOut $saResultsString -Duration $timer.ElapsedMilliseconds 55 | } 56 | } 57 | } 58 | } 59 | catch 60 | { 61 | $ErrorMessage = $_.Exception.Message 62 | $FailedItem = $_.Exception.ItemName 63 | Write-Output $ErrorMessage 64 | Write-Output $FailedItem 65 | Write-Error "The build failed when working with $($resource)." 66 | } 67 | 68 | } 69 | } 70 | 71 | task Test { 72 | ForEach ($unitTest in $unitTests) 73 | { 74 | $testResults = .\Tests\appveyor.pester.ps1 -Test -TestPath $unitTest 75 | 76 | if ($testResults.FailedCount -gt 0) { 77 | $testResults | Format-List 78 | Write-Error -Message 'One or more Pester unit tests failed. Build cannot continue!' 79 | } 80 | } 81 | } 82 | 83 | task MOFTestDeploy -depends Analyze, Test { 84 | try 85 | { 86 | if ($env:APPVEYOR) 87 | { 88 | # copy into the userprofile in appveyor so the module can be loaded 89 | Start-Process -FilePath 'robocopy.exe' -ArgumentList "$PSScriptRoot $env:USERPROFILE\Documents\WindowsPowerShell\Modules\Hubot /S /R:1 /W:1" -Wait -NoNewWindow 90 | } 91 | else 92 | { 93 | # on a local system just create a symlink 94 | New-Item -ItemType SymbolicLink -Path $pathInModuleDir -Target $originalPath -Force | Out-Null 95 | } 96 | } 97 | catch 98 | { 99 | $ErrorMessage = $_.Exception.Message 100 | $FailedItem = $_.Exception.ItemName 101 | Write-Output $ErrorMessage 102 | Write-Output $FailedItem 103 | throw "The build failed when trying prepare files for MOF tests." 104 | } 105 | } 106 | 107 | task MOFTest -depends Analyze, Test, MOFTestDeploy { 108 | ForEach ($moftest in $mofTests) 109 | { 110 | $testResults = .\Tests\appveyor.pester.ps1 -Test -TestPath $moftest 111 | if ($testResults.FailedCount -gt 0) { 112 | $testResults | Format-List 113 | Write-Error -Message 'One or more Pester unit tests failed. Build cannot continue!' 114 | } 115 | } 116 | } 117 | 118 | task BuildArtifact -depends Analyze, Test, MOFTestDeploy, MOFTest { 119 | # Create a clean to build the artifact 120 | New-Item -Path "$PSScriptRoot\Artifact" -ItemType Directory -Force 121 | 122 | # Copy the correct items into the artifacts directory, filtering out the junk 123 | Start-Process -FilePath 'robocopy.exe' -ArgumentList "`"$($PSScriptRoot)`" `"$($PSScriptRoot)\Artifact\Hubot`" /S /R:1 /W:1 /XD Artifact .kitchen .git /XF .gitignore build.ps1 psakeBuild.ps1 *.yml *.xml" -Wait -NoNewWindow 124 | 125 | # Create a zip file artifact 126 | Compress-Archive -Path $PSScriptRoot\Artifact\Hubot -DestinationPath $PSScriptRoot\Artifact\Hubot-$build_version.zip -Force 127 | 128 | if ($env:APPVEYOR) 129 | { 130 | # Push the artifact into appveyor 131 | $zip = Get-ChildItem -Path $PSScriptRoot\Artifact\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 132 | } 133 | } --------------------------------------------------------------------------------