DSCResources/xPSDesiredStateConfiguration/Modules/xPSDesiredStateConfiguration.PSWSIIS/xPSDesiredStateConfiguration.PSWSIIS.psm1
$modulePath = Split-Path -Path $PSScriptRoot -Parent # Import the shared modules Import-Module -Name (Join-Path -Path $modulePath ` -ChildPath (Join-Path -Path 'xPSDesiredStateConfiguration.Common' ` -ChildPath 'xPSDesiredStateConfiguration.Common.psm1')) # Import Localization Strings $script:localizedData = Get-LocalizedData -ResourceName 'xPSDesiredStateConfiguration.PSWSIIS' -ScriptRoot $PSScriptRoot New-Variable -Name DscWebServiceDefaultAppPoolName -Value 'PSWS' -Option ReadOnly -Force -Scope Script <# .SYNOPSIS Validate supplied configuration to setup the PSWS Endpoint Function checks for the existence of PSWS Schema files, IIS config Also validate presence of IIS on the target machine #> function Initialize-Endpoint { [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $appPool, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $site, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $path, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $cfgfile, [Parameter()] [System.Int32] $port, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $app, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $applicationPoolIdentityType, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $svc, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $mof, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $dispatch, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $asax, [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $dependentBinaries, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $language, [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $dependentMUIFiles, [Parameter()] [System.String[]] $psFiles, [Parameter()] [System.Boolean] $removeSiteFiles = $false, [Parameter()] [System.String] $certificateThumbPrint, [Parameter()] [System.Boolean] $enable32BitAppOnWin64 ) if ($certificateThumbPrint -ne 'AllowUnencryptedTraffic') { Write-Verbose -Message 'Verify that the certificate with the provided thumbprint exists in CERT:\LocalMachine\MY\' $certificate = Get-ChildItem -Path CERT:\LocalMachine\MY\ | Where-Object -FilterScript { $_.Thumbprint -eq $certificateThumbPrint } if (!$Certificate) { throw "ERROR: Certificate with thumbprint $certificateThumbPrint does not exist in CERT:\LocalMachine\MY\" } } Test-IISInstall # First remove the site so that the binding count on the application pool is reduced Update-Site -siteName $site -siteAction Remove Remove-AppPool -appPool $appPool # Check for existing binding, there should be no binding with the same port $allWebBindingsOnPort = Get-WebBinding | Where-Object -FilterScript { $_.BindingInformation -eq "*:$($port):" } if ($allWebBindingsOnPort.Count -gt 0) { throw "ERROR: Port $port is already used, please review existing sites and change the port to be used." } if ($removeSiteFiles) { if (Test-Path -Path $path) { Remove-Item -Path $path -Recurse -Force } } Copy-PSWSConfigurationToIISEndpointFolder -path $path ` -cfgfile $cfgfile ` -svc $svc ` -mof $mof ` -dispatch $dispatch ` -asax $asax ` -dependentBinaries $dependentBinaries ` -language $language ` -dependentMUIFiles $dependentMUIFiles ` -psFiles $psFiles New-IISWebSite -site $site ` -path $path ` -port $port ` -app $app ` -apppool $appPool ` -applicationPoolIdentityType $applicationPoolIdentityType ` -certificateThumbPrint $certificateThumbPrint ` -enable32BitAppOnWin64 $enable32BitAppOnWin64 } <# .SYNOPSIS Validate if IIS and all required dependencies are installed on the target machine #> function Test-IISInstall { [CmdletBinding()] param () Write-Verbose -Message 'Checking IIS requirements' $iisVersion = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\InetStp -ErrorAction silentlycontinue).MajorVersion if ($iisVersion -lt 7) { throw "ERROR: IIS Version detected is $iisVersion , must be running higher than 7.0" } $wsRegKey = (Get-ItemProperty hklm:\SYSTEM\CurrentControlSet\Services\W3SVC -ErrorAction silentlycontinue).ImagePath if ($null -eq $wsRegKey) { throw 'ERROR: Cannot retrive W3SVC key. IIS Web Services may not be installed' } if ((Get-Service w3svc).Status -ne 'running') { throw 'ERROR: service W3SVC is not running' } } <# .SYNOPSIS Verify if a given IIS Site exists #> function Test-ForIISSite { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] [System.String] $siteName ) if (Get-Website -Name $siteName) { return $true } return $false } <# .SYNOPSIS Perform an action (such as stop, start, delete) for a given IIS Site #> function Update-Site { param ( [Parameter(ParameterSetName = 'SiteName', Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $siteName, [Parameter(ParameterSetName = 'Site', Mandatory = $true, Position = 0)] [System.Object] $site, [Parameter(ParameterSetName = 'SiteName', Mandatory = $true, Position = 1)] [Parameter(ParameterSetName = 'Site', Mandatory = $true, Position = 1)] [System.String] [ValidateSet('Start', 'Stop', 'Remove')] $siteAction ) if ('SiteName' -eq $PSCmdlet.ParameterSetName) { $site = Get-Website -Name $siteName } if ($site) { switch ($siteAction) { 'Start' { Write-Verbose -Message "Starting IIS Website [$($site.name)]" Start-Website -Name $site.name } 'Stop' { if ('Started' -eq $site.state) { Write-Verbose -Message "Stopping WebSite $($site.name)" $website = Stop-Website -Name $site.name -Passthru if ('Started' -eq $website.state) { throw "Unable to stop WebSite $($site.name)" } <# There may be running requests, wait a little I had an issue where the files were still in use when I tried to delete them #> Write-Verbose -Message 'Waiting for IIS to stop website' Start-Sleep -Milliseconds 1000 } else { Write-Verbose -Message "IIS Website [$($site.name)] already stopped" } } 'Remove' { Update-Site -site $site -siteAction Stop Write-Verbose -Message "Removing IIS Website [$($site.name)]" Remove-Website -Name $site.name } } } else { Write-Verbose -Message "IIS Website [$siteName] not found" } } <# .SYNOPSIS Returns the list of bound sites and applications for a given IIS Application pool .PARAMETER appPool The application pool name #> function Get-AppPoolBinding { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $AppPool ) if (Test-Path -Path "IIS:\AppPools\$AppPool") { $sites = Get-WebConfigurationProperty ` -Filter "/system.applicationHost/sites/site/application[@applicationPool=`'$AppPool`'and @path='/']/parent::*" ` -PSPath 'machine/webroot/apphost' ` -Name name $apps = Get-WebConfigurationProperty ` -Filter "/system.applicationHost/sites/site/application[@applicationPool=`'$AppPool`'and @path!='/']" ` -PSPath 'machine/webroot/apphost' ` -Name path $sites, $apps | ForEach-Object { $_.Value } } } <# .SYNOPSIS Delete the given IIS Application Pool. This is required to cleanup any existing conflicting apppools before setting up the endpoint. #> function Remove-AppPool { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $AppPool ) if ($DscWebServiceDefaultAppPoolName -eq $AppPool) { # Without this tests we may get a breaking error here, despite SilentlyContinue if (Test-Path -Path "IIS:\AppPools\$AppPool") { $bindingCount = (Get-AppPoolBinding -AppPool $AppPool | Measure-Object).Count if (0 -ge $bindingCount) { Remove-WebAppPool -Name $AppPool -ErrorAction SilentlyContinue } else { Write-Verbose -Message "Application pool [$AppPool] can't be deleted because it's still bound to a site or application" } } } else { Write-Verbose -Message "ApplicationPool can't be deleted because the name is different from built-in name [$DscWebServiceDefaultAppPoolName]." } } <# .SYNOPSIS Generate an IIS Site Id while setting up the endpoint. The Site Id will be the max available in IIS config + 1. #> function New-SiteID { [CmdletBinding()] param () return ((Get-Website | Foreach-Object -Process { $_.Id } | Measure-Object -Maximum).Maximum + 1) } <# .SYNOPSIS Copies the supplied PSWS config files to the IIS endpoint in inetpub #> function Copy-PSWSConfigurationToIISEndpointFolder { [CmdletBinding()] param ( [Parameter()] [System.String] $path, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $cfgfile, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $svc, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $mof, [Parameter()] [System.String] $dispatch, [Parameter()] [ValidateScript({Test-Path -Path $_})] [System.String] $asax, [Parameter()] [System.String[]] $dependentBinaries, [Parameter()] [System.String] $language, [Parameter()] [System.String[]] $dependentMUIFiles, [Parameter()] [System.String[]] $psFiles ) if (!(Test-Path -Path $path)) { $null = New-Item -ItemType container -Path $path } foreach ($dependentBinary in $dependentBinaries) { if (!(Test-Path -Path $dependentBinary)) { throw "ERROR: $dependentBinary does not exist" } } Write-Verbose -Message 'Create the bin folder for deploying custom dependent binaries required by the endpoint' $binFolderPath = Join-Path -Path $path -ChildPath 'bin' $null = New-Item -Path $binFolderPath -ItemType 'directory' -Force Copy-Item -Path $dependentBinaries -Destination $binFolderPath -Force foreach ($psFile in $psFiles) { if (!(Test-Path -Path $psFile)) { throw "ERROR: $psFile does not exist" } Copy-Item -Path $psFile -Destination $path -Force } Copy-Item -Path $cfgfile (Join-Path -Path $path -ChildPath 'web.config') -Force Copy-Item -Path $svc -Destination $path -Force Copy-Item -Path $mof -Destination $path -Force if ($dispatch) { Copy-Item -Path $dispatch -Destination $path -Force } if ($asax) { Copy-Item -Path $asax -Destination $path -Force } } <# .SYNOPSIS Setup IIS Apppool, Site and Application #> function New-IISWebSite { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $site, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $path, [Parameter(Mandatory = $true)] [System.Int32] $port, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $app, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $appPool, [Parameter()] [System.String] $applicationPoolIdentityType, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $certificateThumbPrint, [Parameter()] [System.Boolean] $enable32BitAppOnWin64 ) $siteID = New-SiteID if (Test-Path IIS:\AppPools\$appPool) { Write-Verbose -Message "Application Pool [$appPool] already exists" } else { Write-Verbose -Message "Adding App Pool [$appPool]" $null = New-WebAppPool -Name $appPool Write-Verbose -Message 'Set App Pool Properties' $appPoolIdentity = 4 if ($applicationPoolIdentityType) { # LocalSystem = 0, LocalService = 1, NetworkService = 2, SpecificUser = 3, ApplicationPoolIdentity = 4 switch ($applicationPoolIdentityType) { 'LocalSystem' { $appPoolIdentity = 0 } 'LocalService' { $appPoolIdentity = 1 } 'NetworkService' { $appPoolIdentity = 2 } 'ApplicationPoolIdentity' { $appPoolIdentity = 4 } default { throw "Invalid value [$applicationPoolIdentityType] for parameter -applicationPoolIdentityType" } } } $appPoolItem = Get-Item -Path IIS:\AppPools\$appPool $appPoolItem.managedRuntimeVersion = 'v4.0' $appPoolItem.enable32BitAppOnWin64 = $enable32BitAppOnWin64 $appPoolItem.processModel.identityType = $appPoolIdentity $appPoolItem | Set-Item } Write-Verbose -Message 'Add and Set Site Properties' if ($certificateThumbPrint -eq 'AllowUnencryptedTraffic') { $null = New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool } else { $null = New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool -Ssl # Remove existing binding for $port Remove-Item IIS:\SSLBindings\0.0.0.0!$port -ErrorAction Ignore # Create a new binding using the supplied certificate $null = Get-Item CERT:\LocalMachine\MY\$certificateThumbPrint | New-Item IIS:\SSLBindings\0.0.0.0!$port } Update-Site -siteName $site -siteAction Start } <# .SYNOPSIS Enable & Clear PSWS Operational/Analytic/Debug ETW Channels. #> function Enable-PSWSETW { # Disable Analytic Log $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:false /q # Disable Debug Log $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:false /q # Clear Operational Log $null = & $script:wevtutil cl Microsoft-Windows-ManagementOdataService/Operational # Enable/Clear Analytic Log $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:true /q # Enable/Clear Debug Log $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:true /q } <# .SYNOPSIS Create PowerShell WebServices IIS Endpoint .DESCRIPTION Creates a PSWS IIS Endpoint by consuming PSWS Schema and related dependent files .EXAMPLE New PSWS Endpoint [@ http://Server:39689/PSWS_Win32Process] by consuming PSWS Schema Files and any dependent scripts/binaries: New-PSWSEndpoint -site Win32Process -path $env:SystemDrive\inetpub\PSWS_Win32Process -cfgfile Win32Process.config -port 39689 -app Win32Process -svc PSWS.svc -mof Win32Process.mof -dispatch Win32Process.xml -dependentBinaries ConfigureProcess.ps1, Rbac.dll -psFiles Win32Process.psm1 #> function New-PSWSEndpoint { [CmdletBinding()] param ( # Unique Name of the IIS Site [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $site = 'PSWS', # Physical path for the IIS Endpoint on the machine (under inetpub) [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $path = "$env:SystemDrive\inetpub\PSWS", # Web.config file [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $cfgfile = 'web.config', # Port # for the IIS Endpoint [Parameter()] [System.Int32] $port = 8080, # IIS Application Name for the Site [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $app = 'PSWS', # IIS Application Name for the Site [Parameter()] [System.String] $appPool, # IIS App Pool Identity Type - must be one of LocalService, LocalSystem, NetworkService, ApplicationPoolIdentity [Parameter()] [ValidateSet('LocalService', 'LocalSystem', 'NetworkService', 'ApplicationPoolIdentity')] [System.String] $applicationPoolIdentityType, # WCF Service SVC file [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $svc = 'PSWS.svc', # PSWS Specific MOF Schema File [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $mof, # PSWS Specific Dispatch Mapping File [Optional] [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $dispatch, # Global.asax file [Optional] [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $asax, # Any dependent binaries that need to be deployed to the IIS endpoint, in the bin folder [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $dependentBinaries, # MUI Language [Optional] [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $language, # Any dependent binaries that need to be deployed to the IIS endpoint, in the bin\mui folder [Optional] [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $dependentMUIFiles, # Any dependent PowerShell Scipts/Modules that need to be deployed to the IIS endpoint application root [Parameter()] [System.String[]] $psFiles, # True to remove all files for the site at first, false otherwise [Parameter()] [System.Boolean] $removeSiteFiles = $false, # Enable and Clear PSWS ETW [Parameter()] [System.Management.Automation.SwitchParameter] $EnablePSWSETW, # Thumbprint of the Certificate in CERT:\LocalMachine\MY\ for Pull Server [Parameter()] [System.String] $certificateThumbPrint = 'AllowUnencryptedTraffic', # When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine [Parameter()] [System.Boolean] $Enable32BitAppOnWin64 = $false ) if (-not $appPool) { $appPool = $DscWebServiceDefaultAppPoolName } $script:wevtutil = "$env:windir\system32\Wevtutil.exe" $svcName = Split-Path $svc -Leaf $protocol = 'https:' if ($certificateThumbPrint -eq 'AllowUnencryptedTraffic') { $protocol = 'http:' } # Get Machine Name $cimInstance = Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false Write-Verbose -Message "Setting up endpoint at - $protocol//$($cimInstance.Name):$port/$svcName" Initialize-Endpoint ` -appPool $appPool ` -site $site ` -path $path ` -cfgfile $cfgfile ` -port $port ` -app $app ` -applicationPoolIdentityType $applicationPoolIdentityType ` -svc $svc ` -mof $mof ` -dispatch $dispatch ` -asax $asax ` -dependentBinaries $dependentBinaries ` -language $language ` -dependentMUIFiles $dependentMUIFiles ` -psFiles $psFiles ` -removeSiteFiles $removeSiteFiles ` -certificateThumbPrint $certificateThumbPrint ` -enable32BitAppOnWin64 $Enable32BitAppOnWin64 if ($EnablePSWSETW) { Enable-PSWSETW } } <# .SYNOPSIS Removes a DSC WebServices IIS Endpoint .DESCRIPTION Removes a PSWS IIS Endpoint .EXAMPLE Remove the endpoint with the specified name: Remove-PSWSEndpoint -siteName PSDSCPullServer #> function Remove-PSWSEndpoint { [CmdletBinding()] param ( # Unique Name of the IIS Site [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $siteName ) # Get the site to remove $site = Get-Website -Name $siteName if ($site) { # And the pool it is using $pool = $site.applicationPool # Get the path so we can delete the files $filePath = $site.PhysicalPath # Remove the actual site. Update-Site -site $site -siteAction Remove # Remove the files for the site if (Test-Path -Path $filePath) { Get-ChildItem -Path $filePath -Recurse | Remove-Item -Recurse -Force Remove-Item -Path $filePath -Force } Remove-AppPool -appPool $pool } else { Write-Verbose -Message "Website with name [$siteName] does not exist" } } <# .SYNOPSIS Set the option into the web.config for an endpoint .DESCRIPTION Set the options into the web.config for an endpoint allowing customization. #> function Set-AppSettingsInWebconfig { [CmdletBinding()] param ( # Physical path for the IIS Endpoint on the machine (possibly under inetpub) [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Path, # Key to add/update [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Key, # Value [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Value ) $webconfig = Join-Path -Path $Path -ChildPath 'web.config' [System.Boolean] $Found = $false if (Test-Path -Path $webconfig) { $xml = [System.Xml.XmlDocument] (Get-Content -Path $webconfig) $root = $xml.get_DocumentElement() foreach ($item in $root.appSettings.add) { if ($item.key -eq $Key) { $item.value = $Value; $Found = $true; } } if (-not $Found) { $newElement = $xml.CreateElement('add') $nameAtt1 = $xml.CreateAttribute('key') $nameAtt1.psbase.value = $Key; $null = $newElement.SetAttributeNode($nameAtt1) $nameAtt2 = $xml.CreateAttribute('value') $nameAtt2.psbase.value = $Value; $null = $newElement.SetAttributeNode($nameAtt2) $null = $xml.configuration['appSettings'].AppendChild($newElement) } } $xml.Save($webconfig) } <# .SYNOPSIS Set the binding redirect setting in the web.config to redirect 10.0.0.0 version of microsoft.isam.esent.interop to 6.3.0.0. .DESCRIPTION This function creates the following section in the web.config: <runtime> <assemblyBinding xmlns='urn:schemas-microsoft-com:asm.v1'> <dependentAssembly> <assemblyIdentity name='microsoft.isam.esent.interop' publicKeyToken='31bf3856ad364e35' /> <bindingRedirect oldVersion='10.0.0.0' newVersion='6.3.0.0' /> </dependentAssembly> </assemblyBinding> </runtime> #> function Set-BindingRedirectSettingInWebConfig { [CmdletBinding()] param ( # Physical path for the IIS Endpoint on the machine (possibly under inetpub) [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $path, # old version of the assembly [Parameter()] [System.String] $oldVersion = '10.0.0.0', # new version to redirect to [Parameter()] [System.String] $newVersion = '6.3.0.0' ) $webconfig = Join-Path $path 'web.config' if (Test-Path -Path $webconfig) { $xml = [System.Xml.XmlDocument] (Get-Content -Path $webconfig) if (-not($xml.get_DocumentElement().runtime)) { # Create the <runtime> section $runtimeSetting = $xml.CreateElement('runtime') # Create the <assemblyBinding> section $assemblyBindingSetting = $xml.CreateElement('assemblyBinding') $xmlnsAttribute = $xml.CreateAttribute('xmlns') $xmlnsAttribute.Value = 'urn:schemas-microsoft-com:asm.v1' $assemblyBindingSetting.Attributes.Append($xmlnsAttribute) # The <assemblyBinding> section goes inside <runtime> $null = $runtimeSetting.AppendChild($assemblyBindingSetting) # Create the <dependentAssembly> section $dependentAssemblySetting = $xml.CreateElement('dependentAssembly') # The <dependentAssembly> section goes inside <assemblyBinding> $null = $assemblyBindingSetting.AppendChild($dependentAssemblySetting) # Create the <assemblyIdentity> section $assemblyIdentitySetting = $xml.CreateElement('assemblyIdentity') $nameAttribute = $xml.CreateAttribute('name') $nameAttribute.Value = 'microsoft.isam.esent.interop' $publicKeyTokenAttribute = $xml.CreateAttribute('publicKeyToken') $publicKeyTokenAttribute.Value = '31bf3856ad364e35' $null = $assemblyIdentitySetting.Attributes.Append($nameAttribute) $null = $assemblyIdentitySetting.Attributes.Append($publicKeyTokenAttribute) # <assemblyIdentity> section goes inside <dependentAssembly> $dependentAssemblySetting.AppendChild($assemblyIdentitySetting) # Create the <bindingRedirect> section $bindingRedirectSetting = $xml.CreateElement('bindingRedirect') $oldVersionAttribute = $xml.CreateAttribute('oldVersion') $newVersionAttribute = $xml.CreateAttribute('newVersion') $oldVersionAttribute.Value = $oldVersion $newVersionAttribute.Value = $newVersion $null = $bindingRedirectSetting.Attributes.Append($oldVersionAttribute) $null = $bindingRedirectSetting.Attributes.Append($newVersionAttribute) # The <bindingRedirect> section goes inside <dependentAssembly> section $dependentAssemblySetting.AppendChild($bindingRedirectSetting) # The <runtime> section goes inside <Configuration> section $xml.configuration.AppendChild($runtimeSetting) $xml.Save($webconfig) } } } |