diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6fa0242..48e432de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ The format is based on and uses the types of changes according to [Keep a Change and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -- SPSecurityTokenServiceConfig - - Added support for LogonTokenCacheExpirationWindow, WindowsTokenLifetime and FormsTokenLifetime settings ### Added +- SPSecurityTokenServiceConfig + - Added support for LogonTokenCacheExpirationWindow, WindowsTokenLifetime and FormsTokenLifetime settings +- SPService + - New resource - SPUsageDefinition - New resource @@ -20,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [4.6.0] - 2021-04-02 ### Added + - SharePointDsc - Export-SPDscDiagnosticData cmdlet to create a diagnostic package which can easily be shared for troubleshooting diff --git a/SharePointDsc/DSCResources/MSFT_SPSecurityTokenServiceConfig/MSFT_SPSecurityTokenServiceConfig.psm1 b/SharePointDsc/DSCResources/MSFT_SPSecurityTokenServiceConfig/MSFT_SPSecurityTokenServiceConfig.psm1 index ab9ebe887..7c77f2b2f 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSecurityTokenServiceConfig/MSFT_SPSecurityTokenServiceConfig.psm1 +++ b/SharePointDsc/DSCResources/MSFT_SPSecurityTokenServiceConfig/MSFT_SPSecurityTokenServiceConfig.psm1 @@ -282,7 +282,7 @@ function Set-TargetResource -Source $MyInvocation.MyCommand.Source throw $message } - + $config.LogonTokenCacheExpirationWindow = (New-TimeSpan -Minutes $params.LogonTokenCacheExpirationWindow) } diff --git a/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.psm1 b/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.psm1 new file mode 100644 index 000000000..c64fa829f --- /dev/null +++ b/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.psm1 @@ -0,0 +1,256 @@ +$script:SPDscUtilModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SharePointDsc.Util' +Import-Module -Name $script:SPDscUtilModulePath + +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter()] + [System.Management.Automation.PSCredential] + $InstallAccount + ) + + Write-Verbose -Message "Getting status for service '$Name'" + + if ((Get-SPDscInstalledProductVersion).FileMajorPart -eq 15) + { + $message = ("This resource is only supported on SharePoint 2016 and later. " + ` + "SharePoint 2013 does not support MinRole.") + Add-SPDscEvent -Message $message ` + -EntryType 'Error' ` + -EventID 100 ` + -Source $MyInvocation.MyCommand.Source + throw $message + } + + $result = Invoke-SPDscCommand -Credential $InstallAccount ` + -Arguments @($PSBoundParameters) ` + -ScriptBlock { + $params = $args[0] + + $service = Get-SPService -Identity $params.Name -ErrorAction 'SilentlyContinue' + + if ($null -eq $service) + { + return @{ + Name = $params.Name + Ensure = "Absent" + } + } + + if ($service.AutoProvision -eq $true) + { + $localEnsure = "Present" + } + else + { + $localEnsure = "Absent" + } + + return @{ + Name = $params.Name + Ensure = $localEnsure + } + } + return $result +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter()] + [System.Management.Automation.PSCredential] + $InstallAccount + ) + + Write-Verbose -Message "Setting status for service '$Name'" + + if ((Get-SPDscInstalledProductVersion).FileMajorPart -eq 15) + { + $message = ("This resource is only supported on SharePoint 2016 and later. " + ` + "SharePoint 2013 does not support MinRole.") + Add-SPDscEvent -Message $message ` + -EntryType 'Error' ` + -EventID 100 ` + -Source $MyInvocation.MyCommand.Source + throw $message + } + + $invokeArgs = @{ + Credential = $InstallAccount + Arguments = @($PSBoundParameters, $MyInvocation.MyCommand.Source) + } + + if ($Ensure -eq "Present") + { + Write-Verbose -Message "Provisioning service '$Name'" + + Invoke-SPDscCommand @invokeArgs -ScriptBlock { + $params = $args[0] + $eventSource = $args[1] + + $service = Get-SPService -Identity $params.Name -ErrorAction 'SilentlyContinue' + + if ($null -eq $service) + { + $message = "Specified service does not exist '$($params.Name)'" + Add-SPDscEvent -Message $message ` + -EntryType 'Error' ` + -EventID 100 ` + -Source $eventSource + throw $message + } + + Start-SPService -Identity $params.Name | Out-Null + + # Waiting for the service to start before continuing (max 30 minutes) + $serviceCheck = Get-SPService -Identity $params.Name + + $count = 0 + $maxCount = 60 + + while (($count -lt $maxCount) -and ($serviceCheck.CompliantWithMinRole -ne $true)) + { + Write-Verbose -Message ("$([DateTime]::Now.ToShortTimeString()) - Waiting " + ` + "for services to start on all servers. Current status: $($serviceCheck.Status) " + ` + "(waited $count of $maxCount)") + Start-Sleep -Seconds 30 + $serviceCheck = Get-SPService -Identity $params.Name + $count++ + } + } + } + else + { + Write-Verbose -Message "Deprovisioning service '$Name'" + + Invoke-SPDscCommand @invokeArgs -ScriptBlock { + $params = $args[0] + $eventSource = $args[1] + + $service = Get-SPService -Identity $params.Name -ErrorAction 'SilentlyContinue' + + if ($null -eq $service) + { + $message = "Specified service does not exist '$($params.Name)'" + Add-SPDscEvent -Message $message ` + -EntryType 'Error' ` + -EventID 100 ` + -Source $eventSource + throw $message + } + + Stop-SPService -Identity $params.Name -Confirm:$false | Out-Null + + # Waiting for the service to stop before continuing (max 30 minutes) + $serviceCheck = Get-SPService -Identity $params.Name + + $count = 0 + $maxCount = 60 + + while (($count -lt $maxCount) -and ($serviceCheck.AutoProvision -ne $false)) + { + Write-Verbose -Message ("$([DateTime]::Now.ToShortTimeString()) - Waiting " + ` + "for service to stop on all servers. Current status: $($serviceCheck.Status) " + ` + "(waited $count of $maxCount)") + Start-Sleep -Seconds 30 + $serviceCheck = Get-SPService -Identity $params.Name + $count++ + } + } + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = "Present", + + [Parameter()] + [System.Management.Automation.PSCredential] + $InstallAccount + ) + + Write-Verbose -Message "Testing status for service '$Name'" + + $PSBoundParameters.Ensure = $Ensure + + $CurrentValues = Get-TargetResource @PSBoundParameters + + Write-Verbose -Message "Current Values: $(Convert-SPDscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-SPDscHashtableToString -Hashtable $PSBoundParameters)" + + $result = Test-SPDscParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @("Name", "Ensure") + + Write-Verbose -Message "Test-TargetResource returned $result" + + return $result +} + +function Export-TargetResource +{ + $VerbosePreference = "SilentlyContinue" + $ParentModuleBase = Get-Module "SharePointDsc" -ListAvailable | Select-Object -ExpandProperty Modulebase + $module = Join-Path -Path $ParentModuleBase -ChildPath "\DSCResources\MSFT_SPService\MSFT_SPService.psm1" -Resolve + + $Content = '' + $params = Get-DSCFakeParameters -ModulePath $module + + $services = Get-SPService + foreach ($service in $services) + { + $PartialContent = " SPService Service_" + $($service.TypeName -replace " ", '') + "`r`n" + $PartialContent += " {`r`n" + $params.Name = $service.TypeName + $params.Ensure = "Present" + $results = Get-TargetResource @params + + $results = Repair-Credentials -results $results + + $currentBlock = Get-DSCBlock -Params $results -ModulePath $module + $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential" + + $PartialContent += $currentBlock + $PartialContent += " }`r`n" + $Content += $PartialContent + } + + return $Content +} + +Export-ModuleMember -Function *-TargetResource diff --git a/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.schema.mof b/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.schema.mof new file mode 100644 index 000000000..296865300 --- /dev/null +++ b/SharePointDsc/DSCResources/MSFT_SPService/MSFT_SPService.schema.mof @@ -0,0 +1,7 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SPService")] +class MSFT_SPService : OMI_BaseResource +{ + [Key, Description("The name of the service instance to manage")] string Name; + [Write, Description("Present to ensure the service runs in the farm, or absent to ensure it is stopped"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; + [Write, Description("POWERSHELL 4 ONLY: The account to run this resource as, use PsDscRunAsCredential if using PowerShell 5"), EmbeddedInstance("MSFT_Credential")] String InstallAccount; +}; diff --git a/SharePointDsc/DSCResources/MSFT_SPService/readme.md b/SharePointDsc/DSCResources/MSFT_SPService/readme.md new file mode 100644 index 000000000..74d3312f6 --- /dev/null +++ b/SharePointDsc/DSCResources/MSFT_SPService/readme.md @@ -0,0 +1,13 @@ +# Description + +**Type:** Specific +**Requires CredSSP:** No + +This resource is used to specify if a specific service should be provisioned +(Ensure = "Present") or deprovisioned (Ensure = "Absent") in the MinRole +configuration of the farm. The name is the display name of the service as +shown in the "Services in Farm" page in Central Admin: +http://[central_admin_url]/_admin/FarmServices.aspx + +The default value for the Ensure parameter is Present. When not specifying this +parameter, the service instance is started. diff --git a/SharePointDsc/Examples/Resources/SPService/1-StartService.ps1 b/SharePointDsc/Examples/Resources/SPService/1-StartService.ps1 new file mode 100644 index 000000000..1234999cc --- /dev/null +++ b/SharePointDsc/Examples/Resources/SPService/1-StartService.ps1 @@ -0,0 +1,63 @@ + +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID 80d306fa-8bd4-4a8d-9f7a-bf40df95e661 + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS + +.LICENSEURI https://github.com/dsccommunity/SharePointDsc/blob/master/LICENSE + +.PROJECTURI https://github.com/dsccommunity/SharePointDsc + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA + +#> + +<# + +.DESCRIPTION + This example shows how to ensure that the Microsoft SharePoint Foundation + Sandboxed Code Service is provisioned as a MinRole in the farm. + +#> + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [PSCredential] + $SetupAccount + ) + + Import-DscResource -ModuleName SharePointDsc + + node localhost + { + SPService 'Microsoft SharePoint Foundation Sandboxed Code Service' + { + Name = 'Microsoft SharePoint Foundation Sandboxed Code Service' + Ensure = 'Present' + PsDscRunAsCredential = $SetupAccount + } + } +} diff --git a/SharePointDsc/Examples/Resources/SPService/2-StopService.ps1 b/SharePointDsc/Examples/Resources/SPService/2-StopService.ps1 new file mode 100644 index 000000000..5359a3329 --- /dev/null +++ b/SharePointDsc/Examples/Resources/SPService/2-StopService.ps1 @@ -0,0 +1,63 @@ + +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID 80d306fa-8bd4-4a8d-9f7a-bf40df95e661 + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS + +.LICENSEURI https://github.com/dsccommunity/SharePointDsc/blob/master/LICENSE + +.PROJECTURI https://github.com/dsccommunity/SharePointDsc + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA + +#> + +<# + +.DESCRIPTION + This example shows how to ensure that the Microsoft SharePoint Foundation + Sandboxed Code Service is deprovisioned as a MinRole in the farm. + +#> + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [PSCredential] + $SetupAccount + ) + + Import-DscResource -ModuleName SharePointDsc + + node localhost + { + SPService 'Microsoft SharePoint Foundation Sandboxed Code Service' + { + Name = 'Microsoft SharePoint Foundation Sandboxed Code Service' + Ensure = 'Absent' + PsDscRunAsCredential = $SetupAccount + } + } +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f198836bd..a5366e30d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -257,7 +257,7 @@ stages: pathToSources: '$(Build.SourcesDirectory)/$(dscBuildVariable.RepositoryName)/' - script: | - bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/CodeCov_Merged.xml" -F unit + bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/CodeCov_Merged.xml" displayName: 'Upload to Codecov.io' condition: succeededOrFailed() diff --git a/tests/Unit/SharePointDsc/SharePointDsc.SPService.Tests.ps1 b/tests/Unit/SharePointDsc/SharePointDsc.SPService.Tests.ps1 new file mode 100644 index 000000000..d2e2c90d6 --- /dev/null +++ b/tests/Unit/SharePointDsc/SharePointDsc.SPService.Tests.ps1 @@ -0,0 +1,321 @@ +[CmdletBinding()] +param +( + [Parameter()] + [string] + $SharePointCmdletModule = (Join-Path -Path $PSScriptRoot ` + -ChildPath "..\Stubs\SharePoint\15.0.4805.1000\Microsoft.SharePoint.PowerShell.psm1" ` + -Resolve) +) + +$script:DSCModuleName = 'SharePointDsc' +$script:DSCResourceName = 'SPService' +$script:DSCResourceFullName = 'MSFT_' + $script:DSCResourceName + +function Invoke-TestSetup +{ + try + { + Import-Module -Name DscResource.Test -Force + + Import-Module -Name (Join-Path -Path $PSScriptRoot ` + -ChildPath "..\UnitTestHelper.psm1" ` + -Resolve) + + $Global:SPDscHelper = New-SPDscUnitTestHelper -SharePointStubModule $SharePointCmdletModule ` + -DscResource $script:DSCResourceName + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceFullName ` + -ResourceType 'Mof' ` + -TestType 'Unit' +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +try +{ + InModuleScope -ModuleName $script:DSCResourceFullName -ScriptBlock { + Describe -Name $Global:SPDscHelper.DescribeHeader -Fixture { + BeforeAll { + Invoke-Command -ScriptBlock $Global:SPDscHelper.InitializeScript -NoNewScope + + # Mocks for all contexts + Mock -CommandName Start-Sleep -MockWith {} + + function Add-SPDscEvent + { + param ( + [Parameter(Mandatory = $true)] + [System.String] + $Message, + + [Parameter(Mandatory = $true)] + [System.String] + $Source, + + [Parameter()] + [ValidateSet('Error', 'Information', 'FailureAudit', 'SuccessAudit', 'Warning')] + [System.String] + $EntryType, + + [Parameter()] + [System.UInt32] + $EventID + ) + } + } + + # Test contexts + switch ($Global:SPDscHelper.CurrentStubBuildNumber.Major) + { + 15 + { + Context -Name "All methods throw exceptions as MinRole doesn't exist in 2013" -Fixture { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Present" + } + + It "Should throw on the get method" { + { Get-TargetResource @testParams } | Should -Throw "This resource is only supported on SharePoint 2016 and later. SharePoint 2013 does not support MinRole." + } + + It "Should throw on the test method" { + { Test-TargetResource @testParams } | Should -Throw "This resource is only supported on SharePoint 2016 and later. SharePoint 2013 does not support MinRole." + } + + It "Should throw on the set method" { + { Set-TargetResource @testParams } | Should -Throw "This resource is only supported on SharePoint 2016 and later. SharePoint 2013 does not support MinRole." + } + } + } + 16 + { + Context -Name "The service instance is not running but should be" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Present" + } + + Mock -CommandName Start-SPService -MockWith { } + Mock -CommandName Stop-SPService -MockWith { } + + Mock -CommandName Get-SPService -MockWith { + return @{ + AutoProvision = $false + } + } + } + + It "Should return absent from the get method" { + (Get-TargetResource @testParams).Ensure | Should -Be "Absent" + } + + It "Should return false from the set method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should call the start service call from the set method" { + Set-TargetResource @testParams + Assert-MockCalled Start-SPService + } + } + + Context -Name "The service is running and should be" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Present" + } + + Mock -CommandName Start-SPService -MockWith { } + Mock -CommandName Stop-SPService -MockWith { } + + Mock -CommandName Get-SPService -MockWith { + return @{ + AutoProvision = $true + } + } + } + + It "Should return present from the get method" { + (Get-TargetResource @testParams).Ensure | Should -Be "Present" + } + + It "Should return true from the test method" { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "An invalid service is specified to start" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Does not exist" + Ensure = "Present" + } + + Mock -CommandName Get-SPServiceInstance { + return $null + } + } + + It "Should throw when the set method is called" { + { Set-TargetResource @testParams } | Should -Throw "Specified service does not exist '$($testParams.Name)'" + } + } + + Context -Name "The service is not running and should not be" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Absent" + } + + Mock -CommandName Start-SPService -MockWith { } + Mock -CommandName Stop-SPService -MockWith { } + + Mock -CommandName Get-SPService -MockWith { + return @{ + AutoProvision = $false + } + } + } + + It "Should return absent from the get method" { + (Get-TargetResource @testParams).Ensure | Should -Be "Absent" + } + + It "Should return true from the test method" { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The service instance is running and should not be" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Absent" + } + + Mock -CommandName Start-SPService -MockWith { } + Mock -CommandName Stop-SPService -MockWith { } + + Mock -CommandName Get-SPService -MockWith { + return @{ + AutoProvision = $true + } + } + } + + It "Should return present from the get method" { + (Get-TargetResource @testParams).Ensure | Should -Be "Present" + } + + It "Should return false from the set method" { + Test-TargetResource @testParams | Should -Be $false + } + + It "Should call the stop service call from the set method" { + Set-TargetResource @testParams + Assert-MockCalled Stop-SPService + } + } + + Context -Name "An invalid service application is specified to stop" -Fixture { + BeforeAll { + $testParams = @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Absent" + } + + Mock -CommandName Get-SPServiceInstance { + return $null + } + } + + It "Should throw when the set method is called" { + { Set-TargetResource @testParams } | Should -Throw "Specified service does not exist '$($testParams.Name)'" + } + } + + Context -Name "Running ReverseDsc Export" -Fixture { + BeforeAll { + Import-Module (Join-Path -Path (Split-Path -Path (Get-Module SharePointDsc -ListAvailable).Path -Parent) -ChildPath "Modules\SharePointDSC.Reverse\SharePointDSC.Reverse.psm1") + + Mock -CommandName Write-Host -MockWith { } + + Mock -CommandName Get-SPService -MockWith { + return @( + @{ + TypeName = "Microsoft SharePoint Foundation Sandboxed Code Service" + AutoProvision = $true + } + ) + } + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = "Microsoft SharePoint Foundation Sandboxed Code Service" + Ensure = "Present" + } + } + + if ($null -eq (Get-Variable -Name 'spFarmAccount' -ErrorAction SilentlyContinue)) + { + $mockPassword = ConvertTo-SecureString -String "password" -AsPlainText -Force + $Global:spFarmAccount = New-Object -TypeName System.Management.Automation.PSCredential ("contoso\spfarm", $mockPassword) + } + + if ($null -eq (Get-Variable -Name 'DynamicCompilation' -ErrorAction SilentlyContinue)) + { + $DynamicCompilation = $false + } + + if ($null -eq (Get-Variable -Name 'StandAlone' -ErrorAction SilentlyContinue)) + { + $StandAlone = $true + } + + if ($null -eq (Get-Variable -Name 'ExtractionModeValue' -ErrorAction SilentlyContinue)) + { + $Global:ExtractionModeValue = 2 + $Global:ComponentsToExtract = @('SPFarm') + } + + $result = @' + SPService Service_MicrosoftSharePointFoundationSandboxedCodeService + { + Ensure = "Present"; + Name = "Microsoft SharePoint Foundation Sandboxed Code Service"; + PsDscRunAsCredential = $Credsspfarm; + } + +'@ + } + + It "Should return valid DSC block from the Export method" { + Export-TargetResource | Should -Be $result + } + } + } + } + } + } +} +finally +{ + Invoke-TestCleanup +}