diff --git a/src/helpers/chocolateyInstaller.psm1 b/src/helpers/chocolateyInstaller.psm1 index 4741fcf..3e7bcfe 100644 --- a/src/helpers/chocolateyInstaller.psm1 +++ b/src/helpers/chocolateyInstaller.psm1 @@ -3,7 +3,6 @@ $DebugPreference = "SilentlyContinue" if ($env:ChocolateyEnvironmentDebug -eq 'true') {$DebugPreference = "Continue";} - # grab functions from files Resolve-Path $helpersPath\functions\*.ps1 | ? { -not ($_.ProviderPath.Contains(".Tests.")) } | @@ -36,4 +35,12 @@ Export-ModuleMember -Function ` Update-SessionEnvironment,` Get-EnvironmentVariableNames,` Get-EnvironmentVariable,` - Set-EnvironmentVariable + Set-EnvironmentVariable,` + Install-ChocolateyService,` + Get-ServiceExistence,` + Get-ServiceStatus,` + Uninstall-ChocolateyService + +Export-ModuleMember -Function ` + Install-ChocolateyZipPackage -alias ` + Install-ChocolateyArchivePackage \ No newline at end of file diff --git a/src/helpers/functions/Get-ServiceExistence.ps1 b/src/helpers/functions/Get-ServiceExistence.ps1 new file mode 100644 index 0000000..c3aafa7 --- /dev/null +++ b/src/helpers/functions/Get-ServiceExistence.ps1 @@ -0,0 +1,6 @@ +function Get-ServiceExistence { +param( + [string] $serviceName = '' +) + Get-WmiObject -Class Win32_Service -Filter "Name='$serviceName'" +} \ No newline at end of file diff --git a/src/helpers/functions/Get-ServiceStatus.ps1 b/src/helpers/functions/Get-ServiceStatus.ps1 new file mode 100644 index 0000000..2ad4551 --- /dev/null +++ b/src/helpers/functions/Get-ServiceStatus.ps1 @@ -0,0 +1,7 @@ +function Get-ServiceStatus { +param( + [string] $serviceName = '' +) + $serviceStatus = Get-Service -Name $serviceName + $serviceStatus.Status +} \ No newline at end of file diff --git a/src/helpers/functions/Install-ChocolateyService.ps1 b/src/helpers/functions/Install-ChocolateyService.ps1 new file mode 100644 index 0000000..082cf5a --- /dev/null +++ b/src/helpers/functions/Install-ChocolateyService.ps1 @@ -0,0 +1,101 @@ +function Install-ChocolateyService { +<# +.SYNOPSIS +Installs a service + +.DESCRIPTION +This will install a service + +.PARAMETER PackageName +The name of the package for whom the service will be installed. + +.PARAMETER ServiceName +The name of service which will be used to install and start the service. + +.PARAMETER CreateServiceCommand +The command which installs the service. + +.PARAMETER AvailablePort (OPTIONAL) +The port which needs to be available in order to start the service. + +.EXAMPLE +Install-ChocolateyService 'PACKAGE_NAME' 'SERVICE_NAME' 'INSTALL_COMMAND' 'PORT' + +.OUTPUTS +None + +.NOTES +This helper reduces the number of lines one would have to write to install a service to 1 line. +This method has error handling built into it. + +.LINK +Uninstall-ChocolateyService +Get-ServiceExistence +#> +param( + [string] $packageName, + [string] $serviceName, + [string] $createServiceCommand, + [int] $availablePort +) + Write-Debug "Running 'Install-ChocolateyService' for $packageName with serviceName:`'$serviceName`', createServiceCommand: `'$createServiceCommand`', availablePort: `'$availablePort`' "; + + if(!$packageName) { + Write-ChocolateyFailure "Install-ChocolateyService" "Missing PackageName input parameter." + return + } + + if(!$serviceName) { + Write-ChocolateyFailure "Install-ChocolateyService" "Missing ServiceName input parameter." + return + } + + if(!$createServiceCommand) { + Write-ChocolateyFailure "Install-ChocolateyService" "Missing CreateServiceCommand input parameter." + return + } + + try { + Uninstall-ChocolateyService -serviceName "$serviceName" + + try { + Write-Host "$packageName service will be installed" + Write-Host $createServiceCommand + iex $createServiceCommand + } catch { + Write-ChocolateyFailure "Install-ChocolateyService" "The createServiceCommand is incorrect: '$_'." + return + } + + if($availablePort) { + $listeningStatePort = Get-NetTCPConnection -State Listen | Where-Object {$_.LocalAddress -eq "0.0.0.0" -and $_.LocalPort -eq "$availablePort"} + if (!$listeningStatePort) { + return $TRUE + } else { + Write-ChocolateyFailure "Install-ChocolateyService" "$availablePort is in LISTENING state and not available." + return + } + } + + if (Get-ServiceExistence -serviceName "$serviceName") { + Write-Host "$packageName service will be started" + + for ($i=0;$i -lt 12; $i++) { + $serviceStatus = Get-Service -Name $serviceName + + start-service $serviceName + + if ($serviceStatus.Status -eq "running") { + Write-Host "$packageName service has been started" + return + } + Start-Sleep -s 5 + } + } else { + Write-ChocolateyFailure "Install-ChocolateyService" "service $serviceName does not exist." + return + } + } catch { + Write-ChocolateyFailure "Install-ChocolateyService" "There were errors attempting to create the $packageName service. The error message was '$_'." + } +} \ No newline at end of file diff --git a/src/helpers/functions/Install-ChocolateyZipPackage.ps1 b/src/helpers/functions/Install-ChocolateyZipPackage.ps1 index 15481bb..e3d01b1 100644 --- a/src/helpers/functions/Install-ChocolateyZipPackage.ps1 +++ b/src/helpers/functions/Install-ChocolateyZipPackage.ps1 @@ -63,3 +63,5 @@ param( throw } } + +set-alias Install-ChocolateyArchivePackage Install-ChocolateyZipPackage \ No newline at end of file diff --git a/src/helpers/functions/Uninstall-ChocolateyService.ps1 b/src/helpers/functions/Uninstall-ChocolateyService.ps1 new file mode 100644 index 0000000..4b64402 --- /dev/null +++ b/src/helpers/functions/Uninstall-ChocolateyService.ps1 @@ -0,0 +1,11 @@ +function Uninstall-ChocolateyService { +param( + [string] $serviceName = '' +) + if (Get-ServiceExistence -serviceName "$serviceName") { + Write-Host "$serviceName service already exists and will be removed" + stop-service $serviceName + $service = Get-ServiceExistence -serviceName "$serviceName" + $service.delete() + } +} \ No newline at end of file diff --git a/tests/unit/Install-ChocolateyService.Tests.ps1 b/tests/unit/Install-ChocolateyService.Tests.ps1 new file mode 100644 index 0000000..2198bfc --- /dev/null +++ b/tests/unit/Install-ChocolateyService.Tests.ps1 @@ -0,0 +1,108 @@ +$here = Split-Path -Parent $MyInvocation.MyCommand.Definition +$common = Join-Path (Split-Path -Parent $here) '_Common.ps1' +$base = Split-Path -parent (Split-Path -Parent $here) +. $common +. "$base\src\helpers\functions\Install-ChocolateyService.ps1" +. "$base\src\helpers\functions\Get-ServiceExistence.ps1" +. "$base\src\helpers\functions\Get-ServiceStatus.ps1" +. "$base\tests\unit\Install-ChocolateyServiceCorrectParameters.Tests.ps1" + +$availablePort = "135" +$correctServiceName = "installServiceTest" +$unavailableServiceName = "unavailableServiceName" +$testDirectory = "C:\installChocolateyServiceTest" + +Describe "Install-ChocolateyService" { + Context "When provided parameters are correct the service should be created and started" { + Install-ChocolateyServiceCorrectParameters.Tests -testDirectory "$testDirectory" + + It "service creation should succeed" { + Get-ServiceExistence -serviceName "$correctServiceName" | should Be $true + } + + It "service start should succeed" { + Get-ServiceStatus -serviceName "$correctServiceName" -eq "running" | should Be $true + } + } + + Context "When provided parameters are correct and service exist it should be removed, subsequently created and started" { + Install-ChocolateyServiceCorrectParameters.Tests -testDirectory "$testDirectory" + + It "service creation should succeed after deletion of previous service" { + Get-ServiceExistence -serviceName "$correctServiceName" | should Be $true + } + + It "service start should succeed after deletion of previous service" { + Get-ServiceStatus -serviceName "$correctServiceName" -eq "running" | should Be $true + } + } + + Context "When availablePort parameter is passed to this function and it is in LISTENING state and not available" { + Mock Write-ChocolateyFailure + + Install-ChocolateyServiceCorrectParameters.Tests -testDirectory "$testDirectory" -availablePort "$availablePort" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "$availablePort is in LISTENING state and not available." + Write-Host $failureMessage + } + } + } + + Context "When no packageName parameter is passed to this function" { + Mock Write-ChocolateyFailure + + Install-ChocolateyService -serviceName "$unavailableServiceName" -createServiceCommand "$unavailableServiceName" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "Missing PackageName input parameter." } + } + } + + Context "When no serviceName parameter is passed to this function" { + Mock Write-ChocolateyFailure + + Install-ChocolateyService -packageName "$unavailableServiceName" -createServiceCommand "$unavailableServiceName" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "Missing ServiceName input parameter." } + } + } + + Context "When no createServiceCommand parameter is passed to this function" { + Mock Write-ChocolateyFailure + + Install-ChocolateyService -packageName "$unavailableServiceName" -serviceName "$unavailableServiceName" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "Missing CreateServiceCommand input parameter." } + } + } + + Context "When service does not exist" { + Mock Write-ChocolateyFailure + + Install-ChocolateyServiceCorrectParameters.Tests -testDirectory "$testDirectory" -serviceName "$unavailableServiceName" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "service unavailableServiceName does not exist." } + } + } + + Context "When createServiceCommand is incorrect" { + Mock Write-ChocolateyFailure + + Install-ChocolateyServiceCorrectParameters.Tests -testDirectory "$testDirectory" -createServiceCommand "$unavailableServiceName" + + It "should return an error" { + Assert-MockCalled Write-ChocolateyFailure -parameterFilter { $failureMessage -eq "The createServiceCommand is incorrect: 'The term 'unavailableServiceName' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.'." } + } + } + + Write-Host "Remove test directory after finishing testing" + Uninstall-ChocolateyService -serviceName "$serviceName" + + if (Test-Path $testDirectory) { + Remove-Item -Recurse -Force $testDirectory + } +} \ No newline at end of file diff --git a/tests/unit/Install-ChocolateyServiceCorrectParameters.Tests.ps1 b/tests/unit/Install-ChocolateyServiceCorrectParameters.Tests.ps1 new file mode 100644 index 0000000..fadd5a4 --- /dev/null +++ b/tests/unit/Install-ChocolateyServiceCorrectParameters.Tests.ps1 @@ -0,0 +1,21 @@ +function Install-ChocolateyServiceCorrectParameters.Tests { +param( + [string] $testDirectory, + [string] $serviceName = "installServiceTest", + [string] $createServiceCommand = "nssm install installServiceTest", + [int] $availablePort +) + $testServiceBatPath = "$testDirectory\testService.bat" + $testDirectoryExist = Test-Path $testDirectory + $createServiceCommandComplete = "$createServiceCommand `"$testServiceBatPath`"" + + `cinst NSSM` + + if (!$testDirectoryExist) { + Write-Host "$testDirectory directory does not exist and will be created" + New-Item -ItemType Directory -Path $testDirectory + Set-Content -Value "ping localhost" -Path $testServiceBatPath + } + + Install-ChocolateyService -packageName "$serviceName" -serviceName "$serviceName" -createServiceCommand "$createServiceCommandComplete" -availablePort "$availablePort" +} \ No newline at end of file