DSCResources/DscPullServerWeb/DscPullServerWeb.psm1
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCDscTestsPresent', '')] param() ## ## DSC METHODS ## function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $EndpointName, [Parameter(Mandatory = $false)] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $false)] [System.String] $PhysicalPath = "$Env:SystemDrive\inetpub\$EndpointName", [Parameter(Mandatory = $false)] [System.UInt32] $Port = 8090, [Parameter(Mandatory = $true)] [System.String] $CertificateThumbPrint, [Parameter(Mandatory = $false)] [ValidateSet('Anonymous', 'Windows')] [System.String] $AuthenticationMode = 'Anonymous', [Parameter(Mandatory = $false)] [System.String] $AuthorizationGroup = '', [Parameter(Mandatory = $false)] [System.String] $Name = 'Default', [Parameter(Mandatory = $false)] [System.String] $Title = 'DSC Pull Server Web', [Parameter(Mandatory = $false)] [System.String] $Description = 'Website with a REST API to manage the PowerShell DSC web pull server.', [Parameter(Mandatory = $false)] [System.String] $ModulePath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Modules", [Parameter(Mandatory = $false)] [System.String] $ConfigurationPath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Configuration", [Parameter(Mandatory = $false)] [System.String] $DatabasePath = "$Env:ProgramFiles\WindowsPowerShell\DscService", [Parameter(Mandatory = $false)] [System.String] $RegistrationKeyPath = "$Env:ProgramFiles\WindowsPowerShell\DscService" ) if (Test-Path -Path "IIS:\Sites\$EndpointName") { Write-Verbose "Website $EndpointName found, DscPullServerWeb is present." $Ensure = 'Present' # Get Website and the full path for web.config file $websiteItem = Get-Item -Path "IIS:\Sites\$EndpointName" $webConfigPath = Get-Website -Name $EndpointName | Select-Object -ExpandProperty PhysicalPath | Join-Path -ChildPath 'web.config' # Get the IIS port from the binding information and the physical path $PhysicalPath = $websiteItem.physicalPath $Port = $websiteItem.Bindings.Collection[0].BindingInformation.Split(':')[1] $CertificateThumbPrint = $websiteItem.Bindings.Collection[0].CertificateHash # Get authentication and authorization information $AuthenticationMode = Get-WebConfigAuthenticationMode -EndpointName $EndpointName $AuthorizationGroup = Get-WebConfigAuthorizationGroup -Path $webConfigPath # Get the title and description information $Name = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Name' $Title = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Title' $Description = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Description' # Get module, configuration, database and registry key path from the web.config file $ModulePath = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ModulePath' $ConfigurationPath = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ConfigurationPath' $DatabasePath = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'DatabasePath' $RegistrationKeyPath = Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'RegistrationKeyPath' } else { Write-Verbose "Website $EndpointName not found, DscPullServerWeb is absent." $Ensure = 'Absent' $PhysicalPath = '' $Port = 0 $AuthenticationMode = 'Anonymous' $AuthorizationGroup = '' $Name = '' $Title = '' $Description = '' $ModulePath = '' $ConfigurationPath = '' $DatabasePath = '' $RegistrationKeyPath = '' } return @{ EndpointName = $EndpointName Ensure = $Ensure PhysicalPath = $PhysicalPath Port = $Port CertificateThumbPrint = $CertificateThumbPrint AuthenticationMode = $AuthenticationMode AuthorizationGroup = $AuthorizationGroup Name = $Name Title = $Title Description = $Description ModulePath = $ModulePath ConfigurationPath = $ConfigurationPath DatabasePath = $DatabasePath RegistrationKeyPath = $RegistrationKeyPath } } function Set-TargetResource { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([void])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $EndpointName, [Parameter(Mandatory = $false)] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $false)] [System.String] $PhysicalPath = "$Env:SystemDrive\inetpub\$EndpointName", [Parameter(Mandatory = $false)] [System.UInt32] $Port = 8090, [Parameter(Mandatory = $true)] [System.String] $CertificateThumbPrint, [Parameter(Mandatory = $false)] [ValidateSet('Anonymous', 'Windows')] [System.String] $AuthenticationMode = 'Anonymous', [Parameter(Mandatory = $false)] [System.String] $AuthorizationGroup = '', [Parameter(Mandatory = $false)] [System.String] $Name = 'Default', [Parameter(Mandatory = $false)] [System.String] $Title = 'DSC Pull Server Web', [Parameter(Mandatory = $false)] [System.String] $Description = 'Website with a REST API to manage the PowerShell DSC web pull server.', [Parameter(Mandatory = $false)] [System.String] $ModulePath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Modules", [Parameter(Mandatory = $false)] [System.String] $ConfigurationPath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Configuration", [Parameter(Mandatory = $false)] [System.String] $DatabasePath = "$Env:ProgramFiles\WindowsPowerShell\DscService", [Parameter(Mandatory = $false)] [System.String] $RegistrationKeyPath = "$Env:ProgramFiles\WindowsPowerShell\DscService" ) Write-Verbose "Set new configuration for $EndpointName" ## ABSENT: Remove existing website from the local system if ($Ensure -eq 'Absent') { if ($PSCmdlet.ShouldProcess($EndpointName, 'Remove')) { $website = Get-Website -Name $EndpointName if ($null -ne $website) { Write-Verbose "Removing website $EndpointName" $physicalPath = $website.PhysicalPath # Remove the website if it does exist if (Test-Path -Path "IIS:\Sites\$EndpointName") { Write-Verbose 'Remove the IIS website' Remove-Website -Name $EndpointName } # Remove the app pool if it does exist if (Test-Path -Path "IIS:\AppPools\$EndpointName") { Write-Verbose "Remove the IIS app pool" Remove-WebAppPool -Name $EndpointName } # Remove the physical path if (Test-Path -Path $physicalPath) { Write-Verbose 'Remove the physical path' Remove-Item -Path $physicalPath -Confirm:$false -Recurse -Force } # Remove Windows Firewall rule & netsh.exe advfirewall firewall delete rule name=$EndpointName protocol=tcp localport=$Port | Out-Null } } } ## PRESENT: Add website to local system and update configuration if ($Ensure -eq 'Present') { if ($PSCmdlet.ShouldProcess($EndpointName, 'Add')) { $physicalPathSource = $PSScriptRoot | Split-Path | Split-Path | Join-Path -ChildPath 'Binaries\Website' $webConfigPath = Join-Path -Path $PhysicalPath -ChildPath 'web.config' # Create the physical path folder if it does not exist if (-not (Test-Path -Path $PhysicalPath)) { Write-Verbose "Create website physical path $PhysicalPath" New-Item -Path $PhysicalPath -ItemType Directory -Force | Out-Null } # Copy the files to the target folder, if the folder versions are not equal if (-not (Test-WebsiteFile -Source $physicalPathSource -Destination $PhysicalPath)) { Write-Verbose "Deploy website files to $PhysicalPath" # Try to stop the website and the app pool, in order to prevent file locks if (Test-Path -Path "IIS:\Sites\$EndpointName") { Stop-Website -Name $EndpointName -ErrorAction SilentlyContinue } if (Test-Path -Path "IIS:\AppPools\$EndpointName") { Stop-WebAppPool -Name $EndpointName -ErrorAction SilentlyContinue } Get-ChildItem -Path $PhysicalPath -Recurse | Remove-Item -Force Copy-Item -Path "$physicalPathSource\*" -Destination $PhysicalPath -Recurse -Force } # Create the app pool if it does not exist if (-not (Test-Path -Path "IIS:\AppPools\$EndpointName")) { Write-Verbose "Create the app pool $PhysicalPath" New-WebAppPool -Name $EndpointName } # Change the app pool identity to local system if ((Get-Item -Path "IIS:\AppPools\$EndpointName").processModel.identityType -ne 'LocalSystem') { Write-Verbose 'Set the local system account as app pool identity' $appPool = Get-Item -Path "IIS:\AppPools\$EndpointName" $appPool.processModel.identityType = 'LocalSystem' $appPool | Set-Item } # Enable 32 bit applications on 64 bit operations systems if (-not (Get-Item -Path "IIS:\AppPools\$EndpointName").enable32BitAppOnWin64) { Write-Verbose "Enable the option enable32BitAppOnWin64" Set-itemProperty -Path "IIS:\AppPools\$EndpointName" -Name 'enable32BitAppOnWin64' -Value $true } # Create the website if it does not exist if (-not (Test-Path -Path "IIS:\Sites\$EndpointName")) { Write-Verbose 'Create a new IIS website' New-Website -Name $EndpointName -PhysicalPath $PhysicalPath -ApplicationPool $EndpointName -Port $Port -Ssl } # Check and update the certificate binding $binding = (Get-Item -Path "IIS:\Sites\$EndpointName").Bindings.Collection if ($binding.certificateHash -ne $CertificateThumbPrint) { Write-Verbose 'Update the SSL certificate' if (-not ([string]::IsNullOrEmpty($binding.certificateHash))) { $binding.RemoveSslCertificate() } $binding.AddSslCertificate($CertificateThumbPrint, 'My') } # Set authentication and authorization information switch ($AuthenticationMode) { 'Anonymous' { Set-WebConfigAuthenticationMode -EndpointName $EndpointName -Mode 'Anonymous' Disable-WebConfigAuthorizationGroup -Path $webConfigPath } 'Windows' { Set-WebConfigAuthenticationMode -EndpointName $EndpointName -Mode 'Windows' Enable-WebConfigAuthorizationGroup -Path $webConfigPath -Group $AuthorizationGroup } } # Add Windows Firewall rule & netsh.exe advfirewall firewall delete rule name=$EndpointName protocol=tcp localport=$Port | Out-Null & netsh.exe advfirewall firewall add rule name=$EndpointName dir=in action=allow protocol=TCP localport=$Port | Out-Null # Check name, title and description configurations if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Name') -ne $Name) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Name' -AppSettingValue $Name } if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Title') -ne $Title) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Title' -AppSettingValue $Title } if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Description') -ne $Description) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'Description' -AppSettingValue $Description } # Check path configurations if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ModulePath') -ne $ModulePath) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ModulePath' -AppSettingValue $ModulePath } if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ConfigurationPath') -ne $ConfigurationPath) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'ConfigurationPath' -AppSettingValue $ConfigurationPath } if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'DatabasePath') -ne $DatabasePath) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'DatabasePath' -AppSettingValue $DatabasePath } if ((Get-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'RegistrationKeyPath') -ne $RegistrationKeyPath) { Set-WebConfigAppSetting -Path $webConfigPath -AppSettingName 'RegistrationKeyPath' -AppSettingValue $RegistrationKeyPath } # Start Start-Website -Name $EndpointName -ErrorAction SilentlyContinue Start-WebAppPool -Name $EndpointName -ErrorAction SilentlyContinue } } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $EndpointName, [Parameter(Mandatory = $false)] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $false)] [System.String] $PhysicalPath = "$Env:SystemDrive\inetpub\$EndpointName", [Parameter(Mandatory = $false)] [System.UInt32] $Port = 8090, [Parameter(Mandatory = $true)] [System.String] $CertificateThumbPrint, [Parameter(Mandatory = $false)] [ValidateSet('Anonymous', 'Windows')] [System.String] $AuthenticationMode = 'Anonymous', [Parameter(Mandatory = $false)] [System.String] $AuthorizationGroup = '', [Parameter(Mandatory = $false)] [System.String] $Name = 'Default', [Parameter(Mandatory = $false)] [System.String] $Title = 'DSC Pull Server Web', [Parameter(Mandatory = $false)] [System.String] $Description = 'Website with a REST API to manage the PowerShell DSC web pull server.', [Parameter(Mandatory = $false)] [System.String] $ModulePath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Modules", [Parameter(Mandatory = $false)] [System.String] $ConfigurationPath = "$Env:ProgramFiles\WindowsPowerShell\DscService\Configuration", [Parameter(Mandatory = $false)] [System.String] $DatabasePath = "$Env:ProgramFiles\WindowsPowerShell\DscService", [Parameter(Mandatory = $false)] [System.String] $RegistrationKeyPath = "$Env:ProgramFiles\WindowsPowerShell\DscService" ) Write-Verbose "Test current configuration of $EndpointName for desired state" # Get the current configuration $current = Get-TargetResource @PSBoundParameters # Verify in case the desired state is absent if (($Ensure -eq 'Absent') -and ($Ensure -eq $current.Ensure)) { return $true } # Verify in case the desired state is present if (($Ensure -eq 'Present') -and ($Ensure -eq $current.Ensure) -and ($PhysicalPath -eq $current.PhysicalPath) -and ($Port -eq $current.Port) -and ($CertificateThumbPrint -eq $current.CertificateThumbPrint) -and ($AuthenticationMode -eq $current.AuthenticationMode) -and ($AuthorizationGroup -eq $current.AuthorizationGroup) -and ($Name -eq $current.Name) -and ($Title -eq $current.Title) -and ($Description -eq $current.Description) -and ($ModulePath -eq $current.ModulePath) -and ($ConfigurationPath -eq $current.ConfigurationPath) -and ($DatabasePath -eq $current.DatabasePath) -and ($RegistrationKeyPath -eq $current.RegistrationKeyPath)) { return $true } return $false } ## ## AUTHENTICATION AND AUTHORIZATION ## function Get-WebConfigAuthenticationMode { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $EndpointName ) $filter = '/system.webServer/security/authentication' $anonymousAuthentication = Get-WebConfigurationProperty -Location $EndpointName -Filter "$filter/anonymousAuthentication" -Name 'Enabled' $windowsAuthentication = Get-WebConfigurationProperty -Location $EndpointName -Filter "$filter/windowsAuthentication" -Name 'Enabled' if ($anonymousAuthentication.Value -eq $true -and $windowsAuthentication.Value -eq $false) { return 'Anonymous' } if ($anonymousAuthentication.Value -eq $false -and $windowsAuthentication.Value -eq $true) { return 'Windows' } return 'Unknown' } function Set-WebConfigAuthenticationMode { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $EndpointName, [Parameter(Mandatory = $true)] [ValidateSet('Anonymous', 'Windows')] [System.String] $Mode ) $filter = '/system.webServer/security/authentication' switch ($Mode) { 'Anonymous' { if ($PSCmdlet.ShouldProcess('Anonymous', 'Enable')) { Set-WebConfigurationProperty -Location $EndpointName -Filter "$filter/anonymousAuthentication" -Name 'Enabled' -Value $true Set-WebConfigurationProperty -Location $EndpointName -Filter "$filter/windowsAuthentication" -Name 'Enabled' -Value $false } } 'Windows' { if ($PSCmdlet.ShouldProcess('Windows', 'Enable')) { Set-WebConfigurationProperty -Location "$EndpointName" -Filter "$filter/anonymousAuthentication" -Name 'Enabled' -Value $false Set-WebConfigurationProperty -Location "$EndpointName" -Filter "$filter/windowsAuthentication" -Name 'Enabled' -Value $true } } } } function Get-WebConfigAuthorizationGroup { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $Path ) $authorizationGroup = '' if ((Test-Path -Path $Path)) { $webConfigXml = [xml](Get-Content -Path $Path) foreach ($item in $webConfigXml.configuration.'system.web'.authorization.allow) { $authorizationGroup = $item.roles break } } $authorizationGroup } function Enable-WebConfigAuthorizationGroup { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [System.String] $Group ) Disable-WebConfigAuthorizationGroup -Path $Path if ((Test-Path -Path $Path)) { if ($PSCmdlet.ShouldProcess($Group, 'Enable')) { $webConfigXml = [xml](Get-Content -Path $Path) $allowNode = $webConfigXml.CreateNode([System.Xml.XmlNodeType]::Element, 'allow', $null) $allowNode.SetAttribute('roles', $Group) $denyNode = $webConfigXml.CreateNode([System.Xml.XmlNodeType]::Element, 'deny', $null) $denyNode.SetAttribute('users', '*') $authorizationNode = $webConfigXml.CreateNode([System.Xml.XmlNodeType]::Element, 'authorization', $null) $authorizationNode.AppendChild($allowNode) $authorizationNode.AppendChild($denyNode) $webConfigXml.configuration.'system.web'.AppendChild($authorizationNode) $webConfigXml.Save($Path) } } } function Disable-WebConfigAuthorizationGroup { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $Path ) if ((Test-Path -Path $Path)) { if ($PSCmdlet.ShouldProcess($Group, 'Disable')) { $webConfigXml = [xml](Get-Content -Path $Path) if ($null -ne $webConfigXml.configuration.'system.web'.authorization) { foreach ($item in $webConfigXml.configuration.'system.web'.authorization) { $webConfigXml.configuration.'system.web'.RemoveChild($item) } } $webConfigXml.Save($Path) } } } function Get-WebConfigAppSetting { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [System.String] $AppSettingName ) $appSettingValue = '' if ((Test-Path -Path $Path)) { $webConfigXml = [xml](Get-Content -Path $Path) foreach ($item in $webConfigXml.configuration.appSettings.add) { if ($item.key -eq $AppSettingName) { $appSettingValue = $item.value break } } } $appSettingValue } function Set-WebConfigAppSetting { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [System.String] $AppSettingName, [Parameter(Mandatory = $true)] [System.String] $AppSettingValue ) if ((Test-Path -Path $Path)) { if ($PSCmdlet.ShouldProcess($Path, "Update $AppSettingName")) { $webConfigXml = [xml](Get-Content -Path $Path) foreach ($item in $webConfigXml.configuration.appSettings.add) { if ($item.key -eq $AppSettingName) { $item.value = $AppSettingValue } } $webConfigXml.Save($Path) } } } function Test-WebsiteFile { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String] $Source, [Parameter(Mandatory = $true)] [System.String] $Destination ) if (-not (Test-Path -Path "$Destination\bin\DSCPullServerWeb.dll")) { return $false } $sourceVersion = (Get-Item -Path "$Source\bin\DSCPullServerWeb.dll").VersionInfo.FileVersion $destinationVersion = (Get-Item -Path "$Destination\bin\DSCPullServerWeb.dll").VersionInfo.FileVersion return ($sourceVersion -eq $destinationVersion) } Export-ModuleMember -Function *-TargetResource |