Carbon.IIS.psm1
# Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License using module '.\Carbon.IIS.Enums.psm1' using namespace System.Collections using namespace System.Collections.Generic using namespace System.Management.Automation using namespace System.ServiceProcess using namespace Microsoft.Web.Administration #Requires -Version 5.1 Set-StrictMode -Version 'Latest' $InformationPreference = 'Continue' # Functions should use $script:moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $script:moduleRoot = $PSScriptRoot $script:warningMessages = @{} $script:applicationHostPath = Join-Path -Path ([Environment]::SystemDirectory) -ChildPath 'inetsrv\config\applicationHost.config' # These are all the files that could cause the current server manager object to become stale. $script:iisConfigs = & { Join-Path -Path ([Environment]::SystemDirectory) -ChildPath 'inetsrv\config\*.config' Join-Path -Path ([Environment]::GetFolderPath('Windows')) -ChildPath 'Microsoft.NET\Framework*\v*\config\*.config' } $script:skipCommit = $false # Seriously. It has an `IsNumeric` method that isn't part of .NET. Add-Type -AssemblyName 'Microsoft.VisualBasic' $psModulesRoot = Join-Path -Path $script:moduleRoot -ChildPath 'Modules' Import-Module -Name (Join-Path -Path $psModulesRoot -ChildPath 'Carbon.Core' -Resolve) ` -Function @('Add-CTypeData', 'Resolve-CFullPath') Import-Module -Name (Join-Path -Path $psModulesRoot -ChildPath 'Carbon.Windows.HttpServer' -Resolve) ` -Function @('Set-CHttpsCertificateBinding') function Test-MSWebAdministrationLoaded { $serverMgrType = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Location -and ($_.Location | Split-Path -Leaf) -eq 'Microsoft.Web.Administration.dll' } return $null -ne $serverMgrType } $numErrorsAtStart = $Global:Error.Count if( -not (Test-MSWebAdministrationLoaded) ) { $pathsToTry = & { # This is our preferred assembly. Always try it first. if( [Environment]::SystemDirectory ) { $msWebAdminPath = Join-Path -Path ([Environment]::SystemDirectory) ` -ChildPath 'inetsrv\Microsoft.Web.Administration.dll' Get-Item -Path $msWebAdminPath -ErrorAction SilentlyContinue } # If any IIS module is installed, it might have a copy. Find them but make sure they are sorted from # newest version to oldest version. Get-Module -Name 'IISAdministration', 'WebAdministration' -ListAvailable | Select-Object -ExpandProperty 'Path' | Split-Path -Parent | Get-ChildItem -Filter 'Microsoft.Web.Administration.dll' -Recurse -ErrorAction SilentlyContinue | Sort-Object { [Version]$_.VersionInfo.FileVersion } -Descending } foreach( $pathToTry in $pathsToTry ) { try { Add-Type -Path $pathToTry.FullName Write-Debug "Loaded required assembly Microsoft.Web.Administration from ""$($pathToTry)""." break } catch { Write-Debug "Failed to load assembly ""$($pathToTry)"": $($_)." } } } if( -not (Test-MSWebAdministrationLoaded) ) { try { Add-Type -AssemblyName 'Microsoft.Web.Administration' ` -ErrorAction SilentlyContinue ` -ErrorVariable 'addTypeErrors' if( -not $addTypeErrors ) { Write-Debug "Loaded required assembly Microsoft.Web.Administration from GAC." } } catch { } } if( -not (Test-MSWebAdministrationLoaded) ) { Write-Error -Message "Unable to find and load required assembly Microsoft.Web.Administration." -ErrorAction Stop return } $script:serverMgr = [Microsoft.Web.Administration.ServerManager]::New() $script:serverMgrCreatedAt = [DateTime]::UtcNow if( -not $script:serverMgr -or $null -eq $script:serverMgr.ApplicationPoolDefaults ) { Write-Error -Message "Carbon.IIS is not supported on this version of PowerShell." -ErrorAction Stop return } # We successfully loaded Microsoft.Web.Administration assembly, so remove the errors we encountered trying to do so. for( $idx = $Global:Error.Count ; $idx -gt $numErrorsAtStart ; --$idx ) { $Global:Error.RemoveAt(0) } Add-CTypeData -TypeName 'Microsoft.Web.Administration.Site' ` -MemberType ScriptProperty ` -MemberName 'PhysicalPath' ` -Value { $this.Applications | Where-Object 'Path' -EQ '/' | Select-Object -ExpandProperty 'VirtualDirectories' | Where-Object 'Path' -EQ '/' | Select-Object -ExpandProperty 'PhysicalPath' } Add-CTypeData -TypeName 'Microsoft.Web.Administration.Application' ` -MemberType ScriptProperty ` -MemberName 'PhysicalPath' ` -Value { $this.VirtualDirectories | Where-Object 'Path' -EQ '/' | Select-Object -ExpandProperty 'PhysicalPath' } # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = & { Join-Path -Path $script:moduleRoot -ChildPath 'Functions\*.ps1' Join-Path -Path $script:moduleRoot -ChildPath 'Carbon.IIS.ArgumentCompleters.ps1' } foreach ($importPath in $functionsPath) { if( -not (Test-Path -Path $importPath) ) { continue } foreach( $fileInfo in (Get-Item $importPath) ) { . $fileInfo.FullName } } function Add-CIisDefaultDocument { <# .SYNOPSIS Adds a default document name to a website. .DESCRIPTION If you need a custom default document for your website, this function will add it. The `FileName` argument should be a filename IIS should use for a default document, e.g. home.html. If the website already has `FileName` in its list of default documents, this function silently returns. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Add-CIisDefaultDocument -SiteName MySite -FileName home.html Adds `home.html` to the list of default documents for the MySite website. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The name of the site where the default document should be added. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # The default document to add. [Parameter(Mandatory)] [String] $FileName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $section = Get-CIisConfigurationSection -LocationPath $LocationPath -SectionPath 'system.webServer/defaultDocument' if( -not $section ) { return } [Microsoft.Web.Administration.ConfigurationElementCollection] $files = $section.GetCollection('files') $defaultDocElement = $files | Where-Object { $_["value"] -eq $FileName } if ($defaultDocElement) { return } Write-Information "IIS:$($section.LocationPath):$($section.SectionPath) + $($FileName)" $defaultDocElement = $files.CreateElement('add') $defaultDocElement["value"] = $FileName $files.Add( $defaultDocElement ) Save-CIisConfiguration } function Add-CIisHttpHeader { <# .SYNOPSIS Adds an HTTP header to IIS. .DESCRIPTION The `Add-CIisHttpHeader` function adds a header to IIS. Pass the header's name to the `Name` parameter and the header's value to the `Value` parameter. By default, the header is added to all HTTP responses (i.e. IIS's global settings are updated). To add the header only to responses from a specific website, application, virtual directory, or directory, pass the location's path to the `LocationPath` parameter. The function adds the HTTP header to the `system.webServer/httpProtocol/customHeaders` configuration collection. .EXAMPLE Add-CIisHttpHeader -Name 'foo' -Value 'bar' Demonstrates how to add a new HTTP header globally. After the above command runs, this will be in the applicationHost. <system.webServer> <httpProtocol> <customHeaders> <add name="foo" value="bar" /> </customHeaders> </httpProtocol> </system.webServer> .EXAMPLE Add-CIisHttpHeader -LocationPath 'SITE_NAME' -Name 'X-AddHeader' -Value 'usingCarbon' Demonstrates how to add a new HTTP header to the site `SITE_NAME`. After the above command runs, this will be in the applicationHost.config: <location path="SITE_NAME"> <system.webServer> <httpProtocol> <customHeaders> <add name="X-AddHeader" value="usingCarbon" /> </customHeaders> </httpProtocol> </system.webServer> <location path="SITE_NAME" /> #> [CmdletBinding(DefaultParameterSetName='Global')] param( # The HTTP header name to add. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String] $Name, # The HTTP header value to add. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String] $Value, # The location path to the site, directory, appliction, or virtual directory to configure. By default, headers # are added to global configuration. [Parameter(ParameterSetName='Local')] [String] $LocationPath ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $setArgs = @{} if ($LocationPath) { $setArgs['LocationPath'] = $LocationPath } $headers = [List[hashtable]]::New() } process { $headers.Add(@{ 'name' = $Name ; 'value' = $Value }) } end { $headers | Set-CIisCollectionItem -SectionPath 'system.webServer/httpProtocol' ` -CollectionName 'customHeaders' ` @setArgs } } function ConvertTo-CIisVirtualPath { <# .SYNOPSIS Turns a virtual path into a canonical virtual path like you would find in IIS's applicationHost.config .DESCRIPTION The `ConvertTo-CIisVirtualPath` takes in a path and converts it to a canonical virtual path as it would be saved to IIS's applicationHost.config: * duplicate directory separator characters are removed * relative path segments (e.g. `.` or `..`) are resolved and removed (i.e. `path/one/../two` changes to `path/two`) * all `\` characters are converted to `/` * Leading and trailing `/' characters are removed. * Adds a leading `/` character If you don't want a leading `/` character, use the `NoLeadingSlash` switch. .EXAMPLE "/some/path/" | ConvertTo-CIisVirtualPath Would return "/some/path". .EXAMPLE "path" | ConvertTo-CIisVirtualPath Would return "/path" .EXAMPLE "\some\path" | ConvertTo-CIisVirtualPath Would return "/some/path" #> [CmdletBinding()] param( # The path to convert/normalize. [Parameter(Mandatory, ValueFromPipeline)] [AllowNull()] [AllowEmptyString()] [String] $Path, # If true, omits the leading slash on the returned path. The default is to include a leading slash. [switch] $NoLeadingSlash ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $leadingSlash = '/' if( $NoLeadingSlash ) { $leadingSlash = '' } # GetFullPath removes extra slashes, dots but prefixes a path with a root path (e.g. C:\ or /). We need to get # this system's root path so we can use GetFullPath to canonicalize our path, but remove the extra root path # prefix. $root = [IO.Path]::GetFullPath('/') } process { if( -not $Path ) { return $leadingSlash } $indent = ' ' * $Path.Length Write-Debug "$($Path) -->" $prevPath = $Path $Path = $Path.Trim('/', '\') if( $Path -ne $prevPath ) { Write-Debug "$($indent) |- $($Path)" } if (-not $Path) { return $leadingSlash } $prevPath = $Path $Path = $Path | Split-Path -NoQualifier if( $Path -ne $prevPath ) { Write-Debug "$($indent) |- $($Path)" } # [IO.Path]::GetFullPath fails if a path contains certain characters, so we need to escape them, use # GetFullPath, then unescape them. $prevPath = $Path $charsToEscape = @('%', '"', '*', '<', '>', '?', '|') foreach ($invalidChar in $charsToEscape) { $escapeSequence = "%$([byte][char]$invalidChar)" $Path = $Path -replace [regex]::Escape($invalidChar),$escapeSequence } if( [IO.Path]::GetFullPath.OverloadDefinitions.Count -eq 1 ) { $Path = Join-Path -Path $root -ChildPath $Path $Path = [IO.Path]::GetFullPath($Path) } else { $Path = [IO.Path]::GetFullPath($Path, $root) } foreach ($invalidChar in $charsToEscape) { $escapeSequence = "%$([byte][char]$invalidChar)" $Path = $Path -replace $escapeSequence, $invalidChar } $Path = $Path.Substring($root.Length) if( $Path -ne $prevPath ) { Write-Debug "$($indent) |- $($Path)" } $prevPath = $Path $Path = $Path.Replace('\', '/') if( $Path -ne $prevPath ) { Write-Debug "$($indent) |- $($Path)" } $prevPath = $Path $Path = $Path.Trim('\', '/') if( $Path -ne $prevPath ) { Write-Debug "$($indent) |- $($Path)" } $Path = "$($leadingSlash)$($Path)" Write-Debug "$($Path)$(' ' * ([Math]::Max(($indent.Length - $Path.Length), 0))) <--" return $Path } } function Copy-Hashtable { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [Collections.IDictionary] $InputObject, [String[]] $Key ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( -not $Key ) { return $InputObject.Clone() } $newHashtable = @{} foreach( $keyItem in $Key ) { if( -not $InputObject.ContainsKey($keyItem) ) { continue } $newHashtable[$keyItem] = $InputObject[$keyItem] } return $newHashtable } } function Disable-CIisCollectionInheritance { <# .SYNOPSIS Updates IIS configuration collections so they no longer inherit items. .DESCRIPTION The `Disable-CIisCollectionInheritance` function turns off inheritance of items in an IIS configuration collection, i.e. it adds a `<clear />` element to the collection. Pass the path to the configuration section collection to the `SectionPath` parameter. If the collection is actually a sub-element of the configuration section, pass the name of the collection to the `Name` parameter. Inheritance is disabled only if the collection doesn't already have a `<clear />` element. The function reads the applicationHost.config in order to make this determination, since there are no APIs that make this information available. To disable inheritance for a site, directory, application, or virtual directory, pass its location path to the `LocationPath` parameter. To disable inheritance for a collection that is a or is a child of a `[Microsoft.Web.Administration.ConfigurationElement]` (i.e. configuration under a site, application pool, etc.), pass the object to the `ConfigurationElement` parameter. If the object isn't a collection, pass the name of the child element collection to the `Name` parameter. Becuase the Microsoft.Web.Administration API doesn't expose a way to know if a collection has been cleared, the `Disable-CIisCollectionInheritance` function has to inspect the application host config file directly, so you'll also need to pass the XPath expression to the collection element to the `CollectionElementXPath` parameter. When making changes directly to ConfigurationElement objects, test that those changes are saved correctly to the IIS application host configuration. Some configuration has to be saved at the same time as its parent configuration elements are created (i.e. sites, application pools, etc.). Use the `Suspend-CIisAutoCommit` and `Resume-CIisAutoCommit` functions to ensure configuration gets committed simultaneously. .EXAMPLE Disable-CIisCollectionInheritance -SectionPath 'system.webServer/httpProtocol' -Name 'customHeaders' Demonstrates how to disable inheritance on a global collection by passing its configuration section path to the `SectionPath` parameter and the collection name to the `Name` parameter. .EXAMPLE Disable-CIisCollectionInheritance -SectionPath 'system.webServer/httpProtocol' -Name 'customHeaders' -LocationPath 'mysite' Demonstrates how to disable inheritance on a collection under a site, directory, application, or virtual directory by passing the location path to the site, directory, application, or vitual directory to the `LocationPath` parameter. #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The configuration element to configure. If this is the parent element of the collection to configure, pass the # name of the collection child element to the `Name` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The XPath to the configuration element. The Microsoft.Web.Administration API doesn't expose a way to check # if a collection's inheritance is disabled or not, so we have to look directly in the application host config # file. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [String] $CollectionElementXPath, # The configuration section's path who's inheritance to disable. Can be the path to the collection itself, or # the collection's parent element. If passing the parent element, pass the name of the collection to the `Name` # parameter. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # Location path to the site, directory, application, or virtual directory that should be changed. The default is # to modify global configuration. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The name of the collection. [String] $Name ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $PSBoundParameters.Remove('CollectionElementXPath') | Out-Null $collection = Get-CIisCollection @PSBoundParameters if (-not $collection) { return } $testArgs = @{} $displayPath = $collection.ElementTagName if ($PSCmdlet.ParameterSetName -eq 'BySectionPath') { $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath -LocationPath $LocationPath $CollectionElementXPath = $SectionPath.Trim('/') if ($Name) { $CollectionElementXPath = "${CollectionElementXPath}/$($Name.Trim('/'))" } if ($LocationPath) { $testArgs['LocationPath'] = $LocationPath } } if (-not $collection.AllowsClear) { $msg = "Failed to clear collection ${displayPath} because it does not allow clearing." Write-Message $msg -ErrorAction $ErrorActionPreference return } # The Microsoft.Web.Administration API does not expose any way of determining if a collection has a `clear` # element, so we have to crack open the applicationHost.config file to look for it. :( if (Test-CIisApplicationHostElement -XPath "${CollectionElementXPath}/clear" @testArgs) { Write-Verbose "IIS configuration collection ${displayPath} inheritance already disabled." return } Write-Information "Disabling IIS collection inheritance for ${displayPath}." $collection.Clear() Save-CIisConfiguration } function Disable-CIisSecurityAuthentication { <# .SYNOPSIS Disables anonymous, basic, or Windows authentication for all or part of a website. .DESCRIPTION The `Disable-CIisSecurityAuthentication` function disables anonymous, basic, or Windows authentication for a website, application, virtual directory, or directory. Pass the path to the `LocationPath` parameter. Use the `Anonymous` switch to disable anonymous authentication, the `Basic` switch to disable basic authentication, or the `Windows` switch to disable Windows authentication. .LINK Enable-CIisSecurityAuthentication .LINK Get-CIisSecurityAuthentication .LINK Test-CIisSecurityAuthentication .EXAMPLE Disable-CIisSecurityAuthentication -LocationPath 'Peanuts' -Anonymous Turns off anonymous authentication for the `Peanuts` website. .EXAMPLE Disable-CIisSecurityAuthentication -LocationPath 'Peanuts/Snoopy/DogHouse' -Basic Turns off basic authentication for the `Snoopy/DogHouse` directory under the `Peanuts` website. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The location path to the website, directory, application, or virtual directory where authentication should be # disabled. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # Disable anonymous authentication. [Parameter(Mandatory, ParameterSetName='anonymousAuthentication')] [switch] $Anonymous, # Disable basic authentication. [Parameter(Mandatory, ParameterSetName='basicAuthentication')] [switch] $Basic, # Disable Windows authentication. [Parameter(Mandatory, ParameterSetName='windowsAuthentication')] [switch] $Windows ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } $sectionPath = "system.webServer/security/authentication/$($PSCmdlet.ParameterSetName)" Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath $sectionPath ` -Name 'enabled' ` -Value $false } function Enable-CIisDirectoryBrowsing { <# .SYNOPSIS Enables directory browsing under all or part of a website. .DESCRIPTION Enables directory browsing (i.e. showing the contents of a directory by requesting that directory in a web browser) for a website. To enable directory browsing on a directory under the website, pass the virtual path to that directory as the value to the `Directory` parameter. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Enable-CIisDirectoryBrowsing -SiteName Peanuts Enables directory browsing on the `Peanuts` website. .EXAMPLE Enable-CIisDirectoryBrowsing -SiteName Peanuts -Directory Snoopy/DogHouse Enables directory browsing on the `/Snoopy/DogHouse` directory under the `Peanuts` website. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The location path to the website, directory, application, or virtual directory where directory browsing should # be enabled. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath 'system.webServer/directoryBrowse' ` -Name 'enabled' ` -Value $true } function Enable-CIisHttps { <# .SYNOPSIS Turns on and configures HTTPS for a website or part of a website. .DESCRIPTION This function enables HTTPS and optionally the site/directory to: * Require HTTPS (the `RequireHttps` switch) * Ignore/accept/require client certificates (the `AcceptClientCertificates` and `RequireClientCertificates` switches). * Requiring 128-bit HTTPS (the `Require128BitHttps` switch). By default, this function will enable HTTPS, make HTTPS connections optional, ignores client certificates, and not require 128-bit HTTPS. Changing any HTTPS settings will do you no good if the website doesn't have an HTTPS binding or doesn't have an HTTPS certificate. The configuration will most likely succeed, but won't work in a browser. So sad. Beginning with IIS 7.5, the `Require128BitHttps` parameter won't actually change the behavior of a website since [there are no longer 128-bit crypto providers](https://forums.iis.net/p/1163908/1947203.aspx) in versions of Windows running IIS 7.5. .LINK http://support.microsoft.com/?id=907274 .LINK Set-CIisWebsiteHttpsCertificate .EXAMPLE Enable-CIisHttps -LocationPath 'Peanuts' Enables HTTPS on the `Peanuts` website's, making makes HTTPS connections optional, ignoring client certificates, and making 128-bit HTTPS optional. .EXAMPLE Enable-CIisHttps -LocationPath 'Peanuts/Snoopy/DogHouse' -RequireHttps Configures the `/Snoopy/DogHouse` directory in the `Peanuts` site to require HTTPS. It also turns off any client certificate settings and makes 128-bit HTTPS optional. .EXAMPLE Enable-CIisHttps -LocationPath 'Peanuts' -AcceptClientCertificates Enables HTTPS on the `Peanuts` website and configures it to accept client certificates, makes HTTPS optional, and makes 128-bit HTTPS optional. .EXAMPLE Enable-CIisHttps -LocationPath 'Peanuts' -RequireHttps -RequireClientCertificates Enables HTTPS on the `Peanuts` website and configures it to require HTTPS and client certificates. You can't require client certificates without also requiring HTTPS. .EXAMPLE Enable-CIisHttps -LocationPath 'Peanuts' -Require128BitHttps Enables HTTPS on the `Peanuts` website and require 128-bit HTTPS. Also, makes HTTPS connections optional and ignores client certificates. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='IgnoreClientCertificates')] param( # The website whose HTTPS flags should be modifed. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # Should HTTPS be required? [Parameter(ParameterSetName='IgnoreClientCertificates')] [Parameter(ParameterSetName='AcceptClientCertificates')] [Parameter(Mandatory, ParameterSetName='RequireClientCertificates')] [switch] $RequireHttps, # Requires 128-bit HTTPS. Only changes IIS behavior in IIS 7.0. [switch] $Require128BitHttps, # Should client certificates be accepted? [Parameter(Mandatory, ParameterSetName='AcceptClientCertificates')] [switch] $AcceptClientCertificates, # Should client certificates be required? Also requires HTTPS ('RequireHttps` switch). [Parameter(Mandatory, ParameterSetName='RequireClientCertificates')] [switch] $RequireClientCertificates ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $httpsFlags = [CIisHttpsFlags]::None if( $RequireHttps -or $RequireClientCertificates ) { $httpsFlags = $httpsFlags -bor [CIisHttpsFlags]::Ssl } if( $AcceptClientCertificates -or $RequireClientCertificates ) { $httpsFlags = $httpsFlags -bor [CIisHttpsFlags]::SslNegotiateCert } if( $RequireClientCertificates ) { $httpsFlags = $httpsFlags -bor [CIisHttpsFlags]::SslRequireCert } if( $Require128BitHttps ) { $httpsFlags = $httpsFlags -bor [CIisHttpsFlags]::Ssl128 } Set-CIisConfigurationAttribute -LocationPath (Join-CIisPath $LocationPath,$VirtualPath) ` -SectionPath 'system.webServer/security/access' ` -Name 'sslFlags' ` -Value $httpsFlags } function Enable-CIisSecurityAuthentication { <# .SYNOPSIS Enables anonymous, basic, or Windows authentication for an entire site or a sub-directory of that site. .DESCRIPTION The `Enable-CIisSecurityAuthentication` function enables anonymous, basic, or Windows authentication for a website, application, virtual directory, or directory. Pass the location's path to the `LocationPath` parameter. Use the `Anonymous` switch to enable anonymous authentication, the `Basic` switch to enable basic authentication, or the `Windows` switch to enable Windows authentication. .LINK Disable-CIisSecurityAuthentication .LINK Get-CIisSecurityAuthentication .LINK Test-CIisSecurityAuthentication .EXAMPLE Enable-CIisSecurityAuthentication -LocationPath 'Peanuts' -Anonymous Turns on anonymous authentication for the `Peanuts` website. .EXAMPLE Enable-CIisSecurityAuthentication -LocationPath 'Peanuts/Snoopy/DogHouse' -Basic Turns on anonymous authentication for the `Snoopy/DogHouse` directory under the `Peanuts` website. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The location path to the website, application, virtual directory, or directory where the authentication # method should be enabled. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # Enable anonymous authentication. [Parameter(Mandatory, ParameterSetName='anonymousAuthentication')] [switch] $Anonymous, # Enable basic authentication. [Parameter(Mandatory, ParameterSetName='basicAuthentication')] [switch] $Basic, # Enable Windows authentication. [Parameter(Mandatory, ParameterSetName='windowsAuthentication')] [switch] $Windows ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } $sectionPath = "system.webServer/security/authentication/$($PSCmdlet.ParameterSetName)" Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath $sectionPath ` -Name 'enabled' ` -Value $true } function Get-CIisApplication { <# .SYNOPSIS Gets an IIS application as an `Application` object. .DESCRIPTION Uses the `Microsoft.Web.Administration` API to get an IIS application object. If the application doesn't exist, `$null` is returned. If you make any changes to any of the objects returned by `Get-CIisApplication`, call `Save-CIisConfiguration` to save those changes to IIS. The objects returned each have a `PhysicalPath` property which is the physical path to the application. .OUTPUTS Microsoft.Web.Administration.Application. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar` Gets all the applications running under the `DeathStar` website. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar' -VirtualPath '/' Demonstrates how to get the main application for a website: use `/` as the application name. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar' -VirtualPath 'MainPort/ExhaustPort' Demonstrates how to get a nested application, i.e. gets the application at `/MainPort/ExhaustPort` under the `DeathStar` website. #> [CmdletBinding(DefaultParameterSetName='AllApplications')] [OutputType([Microsoft.Web.Administration.Application])] param( # The site where the application is running. [Parameter(Mandatory, ParameterSetName='SpecificApplication')] [String] $SiteName, # The path/name of the application. Default is to return all applications running under the website given by # the `SiteName` parameter. Wildcards supported. [Parameter(ParameterSetName='SpecificApplication')] [Alias('Name')] [String] $VirtualPath, [Parameter(Mandatory, ParameterSetName='Defaults')] [switch] $Defaults ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'Defaults') { return (Get-CIisServerManager).ApplicationDefaults } $site = Get-CIisWebsite -Name $SiteName if( -not $site ) { return } $VirtualPath = $VirtualPath | ConvertTo-CIisVirtualPath $site.Applications | Where-Object { if ($PSBoundParameters.ContainsKey('VirtualPath')) { return ($_.Path -like $VirtualPath) } return $true } } function Get-CIisAppPool { <# .SYNOPSIS Gets IIS application pools. .DESCRIPTION The `Get-CIisAppPool` function returns all IIS application pools that are installed on the current computer. To get a specific application pool, pass its name to the `Name` parameter. If the application pool doesn't exist, an error is written and nothing is returned. You can get the application pool defaults settings by using the `Defaults` switch. If `Defaults` is true, then the `Name` parameter is ignored. If you make any changes to any of the objects returned by `Get-CIisAppPool`, call the `Save-CIisConfiguration` function to save those changes to IIS. This function disposes the current server manager object that Carbon.IIS uses internally. Make sure you have no pending, unsaved changes when calling `Get-CIisAppPool`. .LINK http://msdn.microsoft.com/en-us/library/microsoft.web.administration.applicationpool(v=vs.90).aspx .OUTPUTS Microsoft.Web.Administration.ApplicationPool. .EXAMPLE Get-CIisAppPool Demonstrates how to get *all* application pools. .EXAMPLE Get-CIisAppPool -Name 'Batcave' Gets the `Batcave` application pool. .EXAMPLE Get-CIisAppPool -Defaults Demonstrates how to get IIS application pool defaults settings. #> [CmdletBinding(DefaultParameterSetName='AllAppPools')] [OutputType([Microsoft.Web.Administration.ApplicationPool])] param( # The name of the application pool to return. If not supplied, all application pools are returned. Wildcards # supported. [Parameter(Mandatory, ParameterSetName='AppPoolByWildcard', Position=0)] [String] $Name, # Instead of getting app pools or a specific app pool, return application pool defaults settings. If true, the # `Name` parameter is ignored. [Parameter(Mandatory, ParameterSetName='Defaults')] [switch] $Defaults ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $WhatIfPreference = $false $mgr = Get-CIisServerManager if( $Defaults ) { return $mgr.ApplicationPoolDefaults } $appPools = @() $mgr.ApplicationPools | Where-Object { if ($Name) { return $_.Name -like $Name } return $true } | Tee-Object -Variable 'appPools' | Write-Output if (($Name -and -not [wildcardpattern]::ContainsWildcardCharacters($Name) -and -not $appPools)) { $msg = "IIS application pool ""$($Name)"" does not exist." Write-Error $msg -ErrorAction $ErrorActionPreference } } function Get-CIisCollection { <# .SYNOPSIS Gets an IIS configuration collection. .DESCRIPTION The `Get-CIisCollection` function gets an IIS configuration element as a collection. Pass the collection's IIS configuration section path to the `SectionPath` parameter. If the collection is actually a child element of the configuration section, pass the name of the child element collection to the `Name` parameter. To get the section for a specific site, directory, application, or virtual directory, pass its location path to the `LocationPath` parameter. You can pass an instance of a `[Microsoft.Web.Administration.ConfigurationElement]` to the `ConfigurationElement` parameter to return that element as a collection, or, with the `Name` parameter, get a named collection under that configuration element. This function returns a configuration element collection object. To get the items from the collection, use `Get-CIisCollectionItem`. .EXAMPLE $collection = Get-CIisCollection -LocationPath 'SITE_NAME' -SectionPath 'system.webServer/httpProtocol' -Name 'customHeaders' Demonstrates how to get the collection 'customHeaders' inside the section 'system.webServer/httpProtocol' for the site 'SITE_NAME'. #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The `[Microsoft.Web.Administration.ConfigurationElement]` object to get as a collection or the parent element # of the collection element to get. If this is the parent element, pass the name of the child element collection # to the `Name` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The configuration section path of the collection, or, if the configuration section is a parent of the # collection, the configuration section path to the parent configuration section. If the configuration section # is the parent of the collection, pass the collection name to the `Name` parameter. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The location path to the site, directory, application, or virtual directory to configure. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The collection's name. [String] $Name ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $displayPath = '' if ($ConfigurationElement) { $displayPath = $ConfigurationElement.ElementTagName } else { $getArgs = @{} if ($LocationPath) { $getArgs['LocationPath'] = $LocationPath } $ConfigurationElement = Get-CIisConfigurationSection @getArgs -SectionPath $SectionPath if (-not $ConfigurationElement) { return } $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath -LocationPath $LocationPath -SubSectionPath $Name } if ($Name) { $collection = $ConfigurationElement.GetCollection($Name) } elseif ($ConfigurationElement -is [ICollection]) { $collection = $ConfigurationElement } else { $collection = $ConfigurationElement.GetCollection() } if (-not $collection) { $msg = "Failed to get IIS configuration collection ${displayPath} because it does not exist or is not a " + 'collection' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } # Return the collection itself *not* the collection items. Get-CIisCollectionItem will return individual items. return ,$collection } function Get-CIisCollectionItem { <# .SYNOPSIS Gets the items from an IIS configuration collection. .DESCRIPTION The `Get-CIisCollectionItem` function gets the items from an IIS configuration element collection. Pass the collection's IIS configuration section path to the `SectionPath` parameter. If the configuration section is actually the parent element of the the collection, pass the name of the child element collection to the `CollectionName` parameter. To get the section for a specific site, directory, application, or virtual directory, pass its location path to the `LocationPath` parameter. You can pass an instance of a `[Microsoft.Web.Administration.ConfigurationElement]` to the `ConfigurationElement` parameter to return that collection element's items, or, with the `CollectionName` parameter, get a named collection under that configuration element. This function returns configuration element collection items. To get the collection object itself, use `Get-CIisCollection`. .EXAMPLE $items = Get-CIisCollectionItem -SectionPath 'system.webServer/httpProtocol' -CollectionName 'customHeaders' Demonstrates how to get the custom HTTP headers from the 'customHeaders' collection, which is a child of the "system.webServer/httpProtocol" configuration section. #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The `[Microsoft.Web.Administration.ConfigurationElement]` object whose collection items to get, or the parent # element of the collection whose items to get. If this is the parent element, pass the name of the child # element collection to the `CollectionName` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The configuration section path of the collection, or, if the configuration section is a parent of the # collection, the configuration section path to the parent configuration section. If the configuration section # is the parent of the collection, pass the collection name to the `CollectionName` parameter. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The location path to the site, directory, application, or virtual directory to configure. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The collection's name. [Alias('Name')] [String] $CollectionName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSBoundParameters.ContainsKey('CollectionName')) { $PSBoundParameters['Name'] = $CollectionName $PSBoundParameters.Remove('CollectionName') | Out-Null } Get-CIisCollection @PSBoundParameters | Write-Output } function Get-CIisCollectionKeyName { <# .SYNOPSIS Returns the unique key for a configuration collection. .DESCRIPTION The `Get-CIisCollectionKeyName` locates the mandatory attribute for an IIS configuration collection. This attribute name must be included for all entries inside of an IIS collection. .EXAMPLE Get-CIisCollectionKeyName -Collection (Get-CIisCollection -SectionPath 'system.webServer/httpProtocol' -Name 'customHeaders') Demonstrates how to get the collection key name for the 'system.webServer/httpProtocol/customHeaders' collection. This will return 'name' as the key name. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ConfigurationElement] $Collection ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState return $Collection.CreateElement().Attributes | Where-Object { $_.Schema.IsUniqueKey } | Select-Object -ExpandProperty 'name' } function Get-CIisConfigurationLocationPath { <# .SYNOPSIS Gets the paths of all <location> element from applicationHost.config. .DESCRIPTION The `Get-CIisConfigurationLocationPath` function returns the paths for each `<location>` element in the applicationHost.config file. These location elements are where IIS stores custom configurations for websites and any paths under a website. If this function returns any values, then you know at least one site or site/path has custom configuration. To get the path for a specific website, directory, application, or virtual directory, pass its location path to the `LocationPath` parameter. To get all paths under a website or website/path, use the `-Recurse` switch. If any paths are returned then that site has custom configuration somewhere in its hierarchy. .EXAMPLE Get-CIisConfigurationLocationPath Demonstrates how to get the path for each `<location>` element in the applicationHost.config file, i.e. the paths to each website and path under a website that has custom configuration. .EXAMPLE Get-CIisConfigurationLocationPath -LocationPath 'Default Web Site' Demonstrates how to get the location path for a specific site. .EXAMPLE Get-CIisConfigurationLocationPath -LocationPath 'Default Web Site/some/path' Demonstrates how to get the location path for a specific virtual path under a specific website. .EXAMPLE Get-CIisConfigurationLocationPath -LocationPath 'Default Web Site' -Recurse Demonstrates how to get the location paths for all virtual paths including and under a specific website. .EXAMPLE Get-CIisConfigurationLocationPath -LocationPath 'Default Web Site/some/path' Demonstrates how to get the location paths for all virtual paths including and under a specific virtual path under a specific website. #> [CmdletBinding()] param( # The name of a website whose location paths to get. [Parameter(Position=0)] [String] $LocationPath, # If true, returns all location paths under the website or website/virtual path provided. [switch] $Recurse ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $LocationPath = $LocationPath | ConvertTo-CIisVirtualPath -NoLeadingSlash $mgr = Get-CIisServerManager $mgr.GetApplicationHostConfiguration().GetLocationPaths() | Where-Object { $_ } | Where-Object { return (-not $LocationPath -or $_ -eq $LocationPath -or ($Recurse -and $_ -like "$($LocationPath)/*")) } } function Get-CIisConfigurationSection { <# .SYNOPSIS Gets a Microsoft.Web.Adminisration configuration section for a given site and path. .DESCRIPTION Uses the Microsoft.Web.Administration API to get a `Microsoft.Web.Administration.ConfigurationSection`. .OUTPUTS Microsoft.Web.Administration.ConfigurationSection. .EXAMPLE Get-CIisConfigurationSection -SiteName Peanuts -Path Doghouse -Path 'system.webServer/security/authentication/anonymousAuthentication' Returns a configuration section which represents the Peanuts site's Doghouse path's anonymous authentication settings. #> [CmdletBinding(DefaultParameterSetName='Global')] [OutputType([Microsoft.Web.Administration.ConfigurationSection])] param( # The site whose configuration should be returned. [Parameter(Mandatory, ParameterSetName='ForSite', Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Parameter(ParameterSetName='ForSite')] [Alias('Path')] [String] $VirtualPath, # The path to the configuration section to return. [Parameter(Mandatory, ParameterSetName='ForSite')] [Parameter(Mandatory, ParameterSetName='Global')] [String] $SectionPath, # The type of object to return. Optional. [Type] $Type = [Microsoft.Web.Administration.ConfigurationSection] ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $mgr = Get-CIisServerManager $config = $mgr.GetApplicationHostConfiguration() $section = $null try { if ($PSCmdlet.ParameterSetName -eq 'ForSite') { if ($VirtualPath) { $functionName = $PSCmdlet.MyInvocation.MyCommand.Name $caller = Get-PSCallStack | Select-Object -Skip 1 | Select-Object -First 1 if ($caller.FunctionName -like '*-CIis*') { $functionName = $caller.FunctionName } "The $($functionName) function''s ""SiteName"" and ""VirtualPath"" parameters are obsolete and have " + 'been replaced with a single "LocationPath" parameter, which should be the combined path of the ' + 'location/object to configure, e.g. ' + "``$($functionName) -LocationPath '$($LocationPath)/$($VirtualPath)'``." | Write-CIisWarningOnce $LocationPath = Join-CIisPath -Path $LocationPath, $VirtualPath } $LocationPath = $LocationPath | ConvertTo-CIisVirtualPath $section = $config.GetSection( $SectionPath, $Type, $LocationPath ) } else { $section = $config.GetSection( $SectionPath, $Type ) } } catch { } if( $section ) { if (-not ($section | Get-Member -Name 'LocationPath')) { $section | Add-Member -Name 'LocationPath' -MemberType NoteProperty -Value '' } if ($LocationPath) { $section.LocationPath = $LocationPath } return $section } else { $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath -LocationPath $LocationPath $msg = "IIS configuration section ${displayPath} does not exist." Write-Error $msg -ErrorAction $ErrorActionPreference return } } function Get-CIisDescription { [CmdletBinding()] param( [Parameter(Mandatory, ParameterSetName='ByConfigurationPath')] [ConfigurationElement] $ConfigurationElement, [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, [Parameter(ParameterSetName='BySectionPath')] [String] $SubSectionPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState function Get-LocationDescription { [CmdletBinding()] param( [String] $LocationPath ) if (-not $LocationPath) { return '' } return " for location ""${LocationPath}""" } if ($ConfigurationElement) { $SectionPath = '' $LocationPath = '' $SubSectionPath = '' if ($ConfigurationElement | Get-Member -Name 'SectionPath') { $SectionPath = $ConfigurationElement.SectionPath } $locationDesc = '' if ($ConfigurationElement | Get-Member -Name 'LocationPath') { $LocationPath = $ConfigurationElement.LocationPath } if (-not $SectionPath) { $locationDesc = Get-LocationDescription -LocationPath $LocationPath $name = $ConfigurationElement.Attributes['name'] if ($name) { $name = " ""$($name.Value)""" } else { $name = $ConfigurationElement.Attributes['path'] if ($name) { $name = " $($name.Value)" } } return "IIS configuration element $($ConfigurationElement.ElementTagName)${name}${locationDesc}" } } $sectionDesc = $SectionPath.Trim('/') if ($SubSectionPath) { $sectionDesc = "${sectionDesc}/$($SubSectionPath.Trim('/'))" } $locationDesc = Get-LocationDescription -LocationPath $LocationPath return "IIS configuration section ${sectionDesc}${locationDesc}" } function Get-CIisDisplayPath { [CmdletBinding()] param( [Parameter(Mandatory)] [String] $SectionPath, [String] $LocationPath, [String] $SubSectionPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $path = $SectionPath.Trim('/') if ($LocationPath) { $path = "${LocationPath}:${path}" } if ($SubSectionPath) { $path = "${path}/$($SubSectionPath.Trim('/'))" } return $path } function Get-CIisHttpHeader { <# .SYNOPSIS Gets the HTTP headers for a website or directory under a website. .DESCRIPTION For each custom HTTP header defined under a website and/or a sub-directory under a website, returns an object with these properties: * Name: the name of the HTTP header * Value: the value of the HTTP header .LINK Set-CIisHttpHeader .EXAMPLE Get-CIisHttpHeader -LocationPath SopwithCamel Returns the HTTP headers for the `SopwithCamel` website. .EXAMPLE Get-CIisHttpHeader -LocationPath 'SopwithCamel/Engine' Returns the HTTP headers for the `Engine` directory under the `SopwithCamel` website. .EXAMPLE Get-CIisHttpHeader -LocationPath SopwithCambel -Name 'X-*' Returns all HTTP headers which match the `X-*` wildcard. #> [CmdletBinding()] param( # The name of the website whose headers to return. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # The name of the HTTP header to return. Optional. If not given, all headers are returned. Wildcards # supported. [String] $Name = '*' ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sectionPath = 'system.webServer/httpProtocol' $httpProtocol = Get-CIisConfigurationSection -LocationPath $LocationPath -VirtualPath $VirtualPath -SectionPath $sectionPath $httpProtocol.GetCollection('customHeaders') | Where-Object { $_['name'] -like $Name } | ForEach-Object { $header = [pscustomobject]@{ Name = $_['name']; Value = $_['value'] } $header.pstypenames.Insert(0, 'Carbon.Iis.HttpHeader') $header | Write-Output } } function Get-CIisHttpRedirect { <# .SYNOPSIS Gets the HTTP redirect settings for a website or virtual directory/application under a website. .DESCRIPTION Returns a `[Microsoft.Web.Administration.ConfigurationSection]` object with these attributes: * enabled - `True` if the redirect is enabled, `False` otherwise. * destination - The URL where requests are directed to. * httpResponseCode - The HTTP status code sent to the browser for the redirect. * exactDestination - `True` if redirects are to destination, regardless of the request path. This will send all requests to `Destination`. * childOnly - `True` if redirects are only to content in the destination directory (not subdirectories). Use the `GetAttributeValue` and `SetAttributeValue` to get and set values and the `Save-CIisConfiguration` function to save the changes to IIS. .LINK http://www.iis.net/configreference/system.webserver/httpredirect .EXAMPLE Get-CIisHttpRedirect -LocationPath 'ExampleWebsite' Gets the redirect settings for ExampleWebsite. .EXAMPLE Get-CIisHttpRedirect -LocationPath 'ExampleWebsite/MyVirtualDirectory' Gets the redirect settings for the MyVirtualDirectory virtual directory under ExampleWebsite. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.ConfigurationSection])] param( # The site's whose HTTP redirect settings will be retrieved. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sectionPath = 'system.webServer/httpRedirect' Get-CIisConfigurationSection -LocationPath $LocationPath -VirtualPath $VirtualPath -SectionPath $sectionPath } function Get-CIisMimeMap { <# .SYNOPSIS Gets the file extension to MIME type mappings. .DESCRIPTION IIS won't serve static content unless there is an entry for it in the web server or website's MIME map configuration. This function will return all the MIME maps for the current server. The objects returned have these properties: * `FileExtension`: the mapping's file extension * `MimeType`: the mapping's MIME type Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .LINK Set-CIisMimeMap .EXAMPLE Get-CIisMimeMap Gets all the the file extension to MIME type mappings for the web server. .EXAMPLE Get-CIisMimeMap -FileExtension .htm* Gets all the file extension to MIME type mappings whose file extension matches the `.htm*` wildcard. .EXAMPLE Get-CIisMimeMap -MimeType 'text/*' Gets all the file extension to MIME type mappings whose MIME type matches the `text/*` wildcard. .EXAMPLE Get-CIisMimeMap -LocationPath 'DeathStar' Gets all the file extenstion to MIME type mappings for the `DeathStar` website. .EXAMPLE Get-CIisMimeMap -LocationPath 'DeathStar/ExhaustPort' Gets all the file extension to MIME type mappings for the `DeathStar`'s `ExhausePort` directory. #> [CmdletBinding(DefaultParameterSetName='ForWebServer')] param( # The website whose MIME mappings to return. If not given, returns the web server's MIME map. [Parameter(Mandatory, ParameterSetName='ForWebsite', Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Parameter(ParameterSetName='ForWebsite')] [Alias('Path')] [String] $VirtualPath, # The name of the file extensions to return. Wildcards accepted. [String] $FileExtension = '*', # The name of the MIME type(s) to return. Wildcards accepted. [String] $MimeType = '*' ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getIisConfigSectionParams = @{ } if( $PSCmdlet.ParameterSetName -eq 'ForWebsite' ) { $getIisConfigSectionParams['LocationPath'] = $LocationPath $getIisConfigSectionParams['VirtualPath'] = $VirtualPath } $staticContent = Get-CIisConfigurationSection -SectionPath 'system.webServer/staticContent' @getIisConfigSectionParams $staticContent.GetCollection() | Where-Object { $_['fileExtension'] -like $FileExtension -and $_['mimeType'] -like $MimeType } | ForEach-Object { $mimeMap = [pscustomobject]@{ FileExtension = $_['fileExtension']; MimeType = $_['mimeType'] } $mimeMap.pstypenames.Add('Carbon.Iis.MimeMap') $mimeMap | Write-Output } } function Get-CIisSecurityAuthentication { <# .SYNOPSIS Gets a site's (and optional sub-directory's) security authentication configuration section. .DESCRIPTION You can get the anonymous, basic, digest, and Windows authentication sections by using the `Anonymous`, `Basic`, `Digest`, or `Windows` switches, respectively. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .OUTPUTS Microsoft.Web.Administration.ConfigurationSection. .EXAMPLE Get-CIisSecurityAuthentication -LocationPath 'Peanuts' -Anonymous Gets the `Peanuts` site's anonymous authentication configuration section. .EXAMPLE Get-CIisSecurityAuthentication -LocationPath 'Peanuts/Doghouse' -Basic Gets the `Peanuts` site's `Doghouse` sub-directory's basic authentication configuration section. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.ConfigurationSection])] param( # The site where anonymous authentication should be set. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # Gets a site's (and optional sub-directory's) anonymous authentication configuration section. [Parameter(Mandatory, ParameterSetName='anonymousAuthentication')] [switch] $Anonymous, # Gets a site's (and optional sub-directory's) basic authentication configuration section. [Parameter(Mandatory, ParameterSetName='basicAuthentication')] [switch] $Basic, # Gets a site's (and optional sub-directory's) digest authentication configuration section. [Parameter(Mandatory, ParameterSetName='digestAuthentication')] [switch] $Digest, # Gets a site's (and optional sub-directory's) Windows authentication configuration section. [Parameter(Mandatory, ParameterSetName='windowsAuthentication')] [switch] $Windows ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sectionPath = 'system.webServer/security/authentication/{0}' -f $PSCmdlet.ParameterSetName Get-CIisConfigurationSection -LocationPath $locationPath -VirtualPath $VirtualPath -SectionPath $sectionPath } function Get-CIisServerManager { <# .SYNOPSIS Returns the current instance of the `Microsoft.Web.Administration.ServerManager` class. .DESCRIPTION The `Get-CIisServerManager` function returns the current instance of `Microsoft.Web.Administration.ServerManager` that the Carbon.IIS module is using. After committing changes, the current server manager is destroyed (i.e. its `Dispose` method is called). In case the current server manager is destroyed, `Get-CIisServerManager` will create a new instance of the `Microsoft.Web.Administration.SiteManager` class. After using the server manager, if you've made any changes to any objects referenced from it, call the `Save-CIisConfiguration` function to save/commit your changes. This will properly destroy the server manager after saving/committing your changes. .EXAMPLE $mgr = Get-CIisServerManager Demonstrates how to get the instance of the `Microsoft.Web.Administration.ServerManager` class the Carbon.IIS module is using. #> [CmdletBinding(DefaultParameterSetName='Get')] param( # Saves changes to the current server manager, disposes it, creates a new server manager object, and returns # that new server manager objet. [Parameter(Mandatory, ParameterSetName='Commit')] [switch] $Commit, # Resets and creates a new server manager. Any unsaved changes are lost. [Parameter(Mandatory, ParameterSetName='Reset')] [switch] $Reset, [Parameter(ParameterSetName='Commit')] [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 10) ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState function New-MessagePrefix { return "$(([DateTime]::UtcNow.ToString('O'))) ServerManager #$('{0,-10} ' -f $script:serverMgr.GetHashCode())" } foreach ($config in ($script:iisConfigs | Get-Item)) { if ($script:serverMgrCreatedAt -lt $config.LastWriteTimeUtc) { $Reset = $true "$(New-MessagePrefix)Stale $($script:serverMgrCreatedAt.ToString('O')) < " + "$($config.LastWriteTimeUtc.ToSTring('O')) $($config.FullName)" | Write-Debug break } } if ($Commit -and -not $script:skipCommit) { try { $appHostLastWriteTimeUtc = Get-Item -Path $script:applicationHostPath | Select-Object -ExpandProperty 'LastWriteTimeUtc' Write-Debug "$(New-MessagePrefix)CommitChanges()" Write-Verbose "Committing IIS configuration changes." $serverMgr.CommitChanges() $startedWaitingAt = [Diagnostics.Stopwatch]::StartNew() do { if ($startedWaitingAt.Elapsed -gt $Timeout) { $msg = "Your IIS changes haven't been saved after waiting for $($Timeout) seconds. You may need " + 'to wait a little longer or restart IIS.' Write-Warning $msg break } $appHostInfo = Get-Item -Path $script:applicationHostPath -ErrorAction Ignore if( $appHostInfo -and $appHostLastWriteTimeUtc -lt $appHostInfo.LastWriteTimeUtc ) { Write-Debug " $($startedWaitingAt.Elapsed.TotalSeconds.ToString('0.000'))s Changes committed." $Reset = $true break } Write-Debug " ! $($startedWaitingAt.Elapsed.TotalSeconds.ToString('0.000'))s Waiting." Start-Sleep -Milliseconds 100 } while ($true) } catch { Write-Error $_ -ErrorAction $ErrorActionPreference return } } if ($Reset) { Write-Debug "$(New-MessagePrefix)Dispose()" $script:serverMgr.Dispose() } # It's been disposed. if( -not $script:serverMgr.ApplicationPoolDefaults ) { $script:serverMgr = [Microsoft.Web.Administration.ServerManager]::New() $script:serverMgrCreatedAt = [DateTime]::UtcNow Write-Debug "$(New-MessagePrefix)New()" } Write-Debug "$(New-MessagePrefix)" return $script:serverMgr } function Get-CIisVersion { <# .SYNOPSIS Gets the version of IIS. .DESCRIPTION Reads the version of IIS from the registry, and returns it as a `Major.Minor` formatted string. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Get-CIisVersion Returns `7.0` on Windows 2008, and `7.5` on Windows 7 and Windows 2008 R2. #> [CmdletBinding()] param( ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $props = Get-ItemProperty hklm:\Software\Microsoft\InetStp return $props.MajorVersion.ToString() + "." + $props.MinorVersion.ToString() } function Get-CIisVirtualDirectory { <# .SYNOPSIS Gets an IIS application as an `Application` object. .DESCRIPTION Uses the `Microsoft.Web.Administration` API to get an IIS application object. If the application doesn't exist, `$null` is returned. If you make any changes to any of the objects returned by `Get-CIisApplication`, call `Save-CIisConfiguration` to save those changes to IIS. The objects returned each have a `PhysicalPath` property which is the physical path to the application. .OUTPUTS Microsoft.Web.Administration.Application. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar` Gets all the applications running under the `DeathStar` website. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar' -VirtualPath '/' Demonstrates how to get the main application for a website: use `/` as the application name. .EXAMPLE Get-CIisApplication -SiteName 'DeathStar' -VirtualPath 'MainPort/ExhaustPort' Demonstrates how to get a nested application, i.e. gets the application at `/MainPort/ExhaustPort` under the `DeathStar` website. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.VirtualDirectory])] param( # The site where the application is running. [Parameter(Mandatory, Position=0, ParameterSetName='ByLocationPath')] [String] $LocationPath, # The virtual directory's site's name. [Parameter(Mandatory, ParameterSetName='ByName')] [String] $SiteName, # The virtual directory's virtual path. Wildcards supported. [Parameter(ParameterSetName='ByName')] [String] $VirtualPath, # The virtual directory's application's virtual path. The default is to get the root site's virtual directories. [Parameter(ParameterSetName='ByName')] [String] $ApplicationPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'ByName') { $sites = Get-CIisWebsite -Name $SiteName if (-not $sites) { return } if (-not $ApplicationPath) { $ApplicationPath = '/' } $VirtualPath = $VirtualPath | ConvertTo-CIisVirtualPath foreach ($site in $sites) { $apps = Get-CIisApplication -SiteName $site.Name -VirtualPath $ApplicationPath if (-not $apps) { continue } foreach ($app in $apps) { $appDesc = '' if ($app.Path -ne '/') { $appDesc = " under application ""$($app.Path)""" } $vdir = $app.VirtualDirectories | Where-Object { if ($VirtualPath) { return $_.Path -like $VirtualPath } return $true } if (-not $vdir) { if ($VirtualPath -and -not [wildcardpattern]::ContainsWildcardCharacters($VirtualPath)) { $msg = "Failed to get virtual directory ""${VirtualPath}""${appDesc} under site " + """${SiteName}"" because the virtual directory does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference } continue } $vdir | Write-Output } } return } $siteName, $virtualPath = $LocationPath | Split-CIisLocationPath $site = Get-CIisWebsite -Name $siteName if( -not $site ) { return } $virtualPath = $virtualPath | ConvertTo-CIisVirtualPath foreach ($app in $site.Applications) { foreach ($vdir in $app.VirtualDirectories) { $fullVirtualPath = Join-CIisPath $app.Path, $vdir.Path -LeadingSlash if ($fullVirtualPath -like $virtualPath) { $vdir | Write-Output } } } } function Get-CIisWebsite { <# .SYNOPSIS Returns all the websites installed on the local computer, a specific website, or the website defaults. .DESCRIPTION The `Get-CIisWebsite` function returns all websites installed on the local computer, or nothing if no websites are installed. To get a specific website, pass its name to the `Name` parameter. If a website with that name exists, it is returned as a `Microsoft.Web.Administration.Site` object, from the Microsoft.Web.Administration API. If the website doesn't exist, the function will write an error and return nothing. You can get the website defaults settings by using the `Defaults` switch. If `Defaults` is true, then the `Name` parameter is ignored. If you make any changes to any of the return objects, use `Save-CIisConfiguration` to save your changes. .OUTPUTS Microsoft.Web.Administration.Site. .LINK http://msdn.microsoft.com/en-us/library/microsoft.web.administration.site.aspx .EXAMPLE Get-CIisWebsite Returns all installed websites. .EXAMPLE Get-CIisWebsite -Name 'WebsiteName' Returns the details for the site named `WebsiteName`. .EXAMPLE Get-CIisWebsite -Name 'fubar' -ErrorAction Ignore Demonstrates how to ignore that a website doesn't exist by setting the `ErrorAction` parameter to `Ignore`. .EXAMPLE Get-CIisWebsite -Defaults Demonstrates how to get IIS website defaults settings. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.Site])] param( # The name of the site to get. Wildcards supported. [String] $Name, # Instead of getting all websites or a specifid website, return website defaults settings. If true, the `Name` # parameter is ignored. [switch] $Defaults ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $WhatIfPreference = $false if( $Defaults ) { return (Get-CIisServerManager).SiteDefaults } $sites = @() $mgr = Get-CIisServerManager $mgr.Sites | Where-Object { if( $Name ) { return $_.Name -like $Name } return $true } | Tee-Object -Variable 'sites' | Write-Output if ($Name -and -not [wildcardpattern]::ContainsWildcardCharacters($Name) -and -not $sites) { Write-Error -Message "Website ""$($Name)"" does not exist." -ErrorAction $ErrorActionPreference } } function Install-CIisApplication { <# .SYNOPSIS Creates a new application under a website. .DESCRIPTION Creates a new application at `VirtualPath` under website `SiteName` running the code found on the file system under `PhysicalPath`, i.e. if SiteName is is `example.com`, the application is accessible at `example.com/VirtualPath`. If an application already exists at that path, it is removed first. The application can run under a custom application pool using the optional `AppPoolName` parameter. If no app pool is specified, the application runs under the same app pool as the website it runs under. Beginning with Carbon 2.0, returns a `Microsoft.Web.Administration.Application` object for the new application if one is created or modified. Beginning with Carbon 2.0, if no app pool name is given, existing application's are updated to use `DefaultAppPool`. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Install-CIisApplication -SiteName Peanuts -VirtualPath CharlieBrown -PhysicalPath C:\Path\To\CharlieBrown -AppPoolName CharlieBrownPool Creates an application at `Peanuts/CharlieBrown` which runs from `Path/To/CharlieBrown`. The application runs under the `CharlieBrownPool`. .EXAMPLE Install-CIisApplication -SiteName Peanuts -VirtualPath Snoopy -PhysicalPath C:\Path\To\Snoopy Create an application at Peanuts/Snoopy, which runs from C:\Path\To\Snoopy. It uses the same application as the Peanuts website. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.Application])] param( # The site where the application should be created. [Parameter(Mandatory)] [String] $SiteName, # The path of the application. [Parameter(Mandatory)] [Alias('Name')] [String] $VirtualPath, # The path to the application. [Parameter(Mandatory)] [Alias('Path')] [String] $PhysicalPath, # The app pool for the application. Default is `DefaultAppPool`. [String] $AppPoolName, # Returns IIS application object. This switch is new in Carbon 2.0. [Switch] $PassThru ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $SiteName if( -not $site ) { return } $iisAppPath = Join-CIisPath -Path $SiteName, $VirtualPath $PhysicalPath = Resolve-CFullPath -Path $PhysicalPath if( -not (Test-Path $PhysicalPath -PathType Container) ) { Write-Verbose ('IIS://{0}: creating physical path {1}' -f $iisAppPath,$PhysicalPath) $null = New-Item $PhysicalPath -ItemType Directory } $apps = $site.GetCollection() $msgPrefix = "IIS website ""$($SiteName)"": " $VirtualPath = $VirtualPath | ConvertTo-CIisVirtualPath $app = Get-CIisApplication -SiteName $SiteName -VirtualPath $VirtualPath $modified = $false if( -not $app ) { Write-Information "$($msgPrefix)creating application ""$($VirtualPath)""." $app = $apps.CreateElement('application') $app['path'] = $VirtualPath $apps.Add( $app ) | Out-Null $modified = $true } $msgPrefix = "$($msgPrefix)application ""$($VirtualPath)"": " if( $AppPoolName -and $app['applicationPool'] -ne $AppPoolName ) { Write-Information "$($msgPrefix)Application Pool $($app['applicationPool']) -> $($AppPoolName)" $app['applicationPool'] = $AppPoolName $modified = $true } $vdir = $null if( $app | Get-Member 'VirtualDirectories' ) { $vdir = $app.VirtualDirectories | Where-Object 'Path' -EQ '/' } if( -not $vdir ) { Write-Information "$($msgPrefix)Virtual Directory $('') -> /" $vdirs = $app.GetCollection() $vdir = $vdirs.CreateElement('virtualDirectory') $vdir['path'] = '/' $vdirs.Add( $vdir ) | Out-Null $modified = $true } if( $vdir['physicalPath'] -ne $PhysicalPath ) { Write-Information "$($msgPrefix)Physical Path $($vdir['physicalPath']) -> $($PhysicalPath)" $vdir['physicalPath'] = $PhysicalPath $modified = $true } if( $modified ) { Save-CIisConfiguration } if( $PassThru ) { return Get-CIisApplication -SiteName $SiteName -VirtualPath $VirtualPath } } function Install-CIisAppPool { <# .SYNOPSIS Creates or updates an IIS application pool. .DESCRIPTION The `Install-CIisAppPool` function creates or updates an IIS application pool. Pass the name of the application pool to the `Name` parameter. If that application pool doesn't exist, it is created. If it does exist, its configuration is updated to match the values of the arguments passed. If you don't pass an argument, that argument's setting is deleted and reset to its default value. You always get an application pool with the exact same configuration, even if someone or something has changed an application pool's configuration in some other way. To configure the application pool's process model (i.e. the application pool's account/identity, idle timeout, etc.), use the `Set-CIisAppPoolProcessModel` function. To configure the application pool's periodic restart settings, use the `Set-CIisAppPoolPeriodicRestart` function. To configure the application pool's periodic restart settings, use the `Set-CIisAppPoolPeriodicRestart` can't delete an app pool if there are any websites using it, that's why.) To configure the application pool's CPU settings, use the `Set-CIisAppPoolCpu` function. .EXAMPLE Install-CIisAppPool -Name Cyberdyne Demonstrates how to use Install-CIisAppPool to create/update an application pool with reasonable defaults. In this example, an application pool named "Cyberdyne" is created that is 64-bit, uses .NET 4.0, and an integrated pipeline. .EXAMPLE Install-CIisAppPool -Name Cyberdyne -Enable32BitAppOnWin64 $true -ManagedPipelineMode Classic -ManagedRuntimeVersion 'v2.0' Demonstrates how to customize an application pool away from its default settings. In this example, the "Cyberdyne" application pool is created that is 32-bit, uses .NET 2.0, and a classic pipeline. #> [OutputType([Microsoft.Web.Administration.ApplicationPool])] [CmdletBinding(DefaultParameterSetName='New')] param( # The app pool's name. [Parameter(Mandatory)] [String] $Name, # Sets the IIS application pool's `autoStart` setting. [Parameter(ParameterSetName='New')] [bool] $AutoStart, # Sets the IIS application pool's `CLRConfigFile` setting. [Parameter(ParameterSetName='New')] [String] $CLRConfigFile, # Sets the IIS application pool's `enable32BitAppOnWin64` setting. [Parameter(ParameterSetName='New')] [bool] $Enable32BitAppOnWin64, # Sets the IIS application pool's `enableConfigurationOverride` setting. [Parameter(ParameterSetName='New')] [bool] $EnableConfigurationOverride, # Sets the IIS application pool's `managedPipelineMode` setting. [ManagedPipelineMode] $ManagedPipelineMode, # Sets the IIS application pool's `managedRuntimeLoader` setting. [Parameter(ParameterSetName='New')] [String] $ManagedRuntimeLoader, # Sets the IIS application pool's `managedRuntimeVersion` setting. [String] $ManagedRuntimeVersion, # Sets the IIS application pool's `passAnonymousToken` setting. [Parameter(ParameterSetName='New')] [bool] $PassAnonymousToken, # Sets the IIS application pool's `queueLength` setting. [Parameter(ParameterSetName='New')] [UInt32] $QueueLength, # Sets the IIS application pool's `startMode` setting. [Parameter(ParameterSetName='New')] [StartMode] $StartMode, # Return an object representing the app pool. [switch] $PassThru, #Idle Timeout value in minutes. Default is 0. [Parameter(ParameterSetName='Deprecated')] [ValidateScript({$_ -gt 0})] [int] $IdleTimeout = 0, # Run the app pool under the given local service account. Valid values are `NetworkService`, `LocalService`, # and `LocalSystem`. The default is `ApplicationPoolIdentity`, which causes IIS to create a custom local user # account for the app pool's identity. The default is `ApplicationPoolIdentity`. [Parameter(ParameterSetName='Deprecated')] [ValidateSet('NetworkService', 'LocalService', 'LocalSystem')] [String] $ServiceAccount, # The credential to use to run the app pool. # # The `Credential` parameter is new in Carbon 2.0. [Parameter(ParameterSetName='Deprecated', Mandatory)] [pscredential] $Credential, # Enable 32-bit applications. [Parameter(ParameterSetName='Deprecated')] [switch] $Enable32BitApps, # Use the classic pipeline mode, i.e. don't use an integrated pipeline. [Parameter(ParameterSetName='Deprecated')] [switch] $ClassicPipelineMode ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'Deprecated') { $functionName = $PSCmdlet.MyInvocation.MyCommand.Name $installArgs = @{ 'ManagedPipelineMode' = [ManagedPipelineMode]::Integrated 'ManagedRuntimeVersion' = 'v4.0' } $installArgs['Enable32BitAppOnWin64'] = $Enable32BitApps.IsPresent if ($ClassicPipelineMode) { "The ""$($functionName)"" function's ""ClassicPipelineMode"" switch is deprecated. Use the " + '"ManagedPipelineMode" parameter instead.' | Write-CIIsWarningOnce $installArgs['ManagedPipelineMode'] = [ManagedPipelineMode]::Classic } if ($ManagedRuntimeVersion) { $installArgs['ManagedRuntimeVersion'] = $ManagedRuntimeVersion } if ($PassThru) { $installArgs['PassThru'] = $PassThru } Install-CIisAppPool -Name $Name @installArgs $setProcessModelArgs = @{} if ($Credential) { "The ""$($functionName)"" function's ""Credential"" parameter is deprecated. Use the " + '"Set-CIisAppPoolProcessModel" function and its "IdentityType", "UserName", and "Password" parameters ' + 'instead.' | Write-CIIsWarningOnce $setProcessModelArgs['IdentityType'] = [ProcessModelIdentityType]::SpecificUser $setProcessModelArgs['UserName'] = $Credential.UserName $setProcessModelArgs['Password'] = $Credential.Password } elseif ($ServiceAccount) { "The $($functionName) function's ""ServiceAccount"" parameter is deprecated. Use the " + '"Set-CIisAppPoolProcessModel" function and its "IdentityType" parameter instead.' | Write-CIIsWarningOnce $setProcessModelArgs['IdentityType'] = $ServiceAccount } if ($IdleTimeout) { "The $($functionName) function's ""IdleTimeout"" parameter is deprecated. Use the " + '"Set-CIisAppPoolProcessModel" function and its "IdleTimeout" parameter instead.' | Write-CIIsWarningOnce $setProcessModelArgs['IdleTimeout'] = $IdleTimeout } if ($setProcessModelArgs.Count -eq 0) { return } Set-CIisAppPoolProcessModel -AppPoolName $Name @setProcessModelArgs return } if( -not (Test-CIisAppPool -Name $Name) ) { Write-Information "Creating IIS Application Pool ""$($Name)""." $mgr = Get-CIisServerManager $mgr.ApplicationPools.Add($Name) | Out-Null Save-CIisConfiguration } $setArgs = @{} foreach( $parameterName in (Get-Command -Name 'Set-CIisAppPool').Parameters.Keys ) { if( -not $PSBoundParameters.ContainsKey($parameterName) ) { continue } $setArgs[$parameterName] = $PSBoundParameters[$parameterName] } Set-CIisAppPool @setArgs -Reset Start-CIisAppPool -Name $Name if( $PassThru ) { return (Get-CIisAppPool -Name $Name) } } function Install-CIisVirtualDirectory { <# .SYNOPSIS Installs a virtual directory. .DESCRIPTION The `Install-CIisVirtualDirectory` function creates a virtual directory under website `SiteName` at `VirtualPath`, serving files out of `PhysicalPath`. If a virtual directory at `VirtualPath` already exists, it is updated in place. .EXAMPLE Install-CIisVirtualDirectory -SiteName 'Peanuts' -VirtualPath 'DogHouse' -PhysicalPath C:\Peanuts\Doghouse Creates a `/DogHouse` virtual directory, which serves files from the C:\Peanuts\Doghouse directory. If the Peanuts website responds to hostname `peanuts.com`, the virtual directory is accessible at `peanuts.com/DogHouse`. .EXAMPLE Install-CIisVirtualDirectory -SiteName 'Peanuts' -VirtualPath 'Brown/Snoopy/DogHouse' -PhysicalPath C:\Peanuts\DogHouse Creates a DogHouse virtual directory under the `Peanuts` website at `/Brown/Snoopy/DogHouse` serving files out of the `C:\Peanuts\DogHouse` directory. If the Peanuts website responds to hostname `peanuts.com`, the virtual directory is accessible at `peanuts.com/Brown/Snoopy/DogHouse`. #> [CmdletBinding()] param( # The site where the virtual directory should be created. [Parameter(Mandatory)] [String] $SiteName, # The virtual path of the virtual directory to install, i.e. the path in the URL to this directory. If creating # under an applicaton, this should be the path in the URL *after* the path in the URL to the application. [Parameter(Mandatory)] [Alias('Name')] [String] $VirtualPath, # The path of the application under which the virtual directory should get created. The default is to create # the virtual directory under website's root application, `/`. [String] $ApplicationPath = '/', # The file system path to the virtual directory. [Parameter(Mandatory)] [Alias('Path')] [String] $PhysicalPath, # Deletes the virtual directory before installation, if it exists. # # *Does not* delete custom configuration for the virtual directory, just the virtual directory. If you've # customized the location of the virtual directory, those customizations will remain in place. # # The `Force` switch is new in Carbon 2.0. [switch] $Force ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $SiteName if( -not $site ) { return } $ApplicationPath = $ApplicationPath | ConvertTo-CIisVirtualPath [Microsoft.Web.Administration.Application] $destinationApp = $site.Applications | Where-Object 'Path' -EQ $ApplicationPath if( -not $destinationApp ) { Write-Error ("The ""$($SiteName)"" website's ""$($ApplicationPath)"" application does not exist.") return } $appDesc = '' if ($destinationApp -and $destinationApp.Path -ne '/') { $appDesc = " under application ""$($destinationApp.Path)""" } $PhysicalPath = Resolve-CFullPath -Path $PhysicalPath $VirtualPath = $VirtualPath | ConvertTo-CIisVirtualPath $vPathMsg = Join-CIisPath -Path $ApplicationPath, $VirtualPath $vdir = $destinationApp.VirtualDirectories | Where-Object 'Path' -EQ $VirtualPath if( $Force -and $vdir ) { Write-IisVerbose $SiteName -VirtualPath $vPathMsg 'REMOVE' '' '' $destinationApp.VirtualDirectories.Remove($vdir) Save-CIisConfiguration $vdir = $null $site = Get-CIisWebsite -Name $SiteName $destinationApp = $site.Applications | Where-Object 'Path' -EQ '/' } $desc = "IIS virtual directory ""${VirtualPath}""${appDesc} under site ""${SiteName}""" $created = $false if (-not $vdir) { [Microsoft.Web.Administration.ConfigurationElementCollection]$vdirs = $destinationApp.GetCollection() $vdir = $vdirs.CreateElement('virtualDirectory') Write-Information "Creating ${desc}." Write-Information " + physicalPath ${PhysicalPath}" $vdir['path'] = $VirtualPath [void]$vdirs.Add( $vdir ) $created = $true } $modified = $false if ($vdir['physicalPath'] -ne $PhysicalPath) { $vdir['physicalPath'] = $PhysicalPath if (-not $created) { Write-Information $desc Write-Information " physicalPath $($vdir['physicalPath']) -> ${PhysicalPath}" } $modified = $true } if ($created -or $modified) { Save-CIIsConfiguration } } function Install-CIisWebsite { <# .SYNOPSIS Installs a website. .DESCRIPTION `Install-CIisWebsite` installs an IIS website. Anonymous authentication is enabled, and the anonymous user is set to the website's application pool identity. Before Carbon 2.0, if a website already existed, it was deleted and re-created. Beginning with Carbon 2.0, existing websites are modified in place. If you don't set the website's app pool, IIS will pick one for you (usually `DefaultAppPool`), an `Install-CIisWebsite` will never manage the app pool for you (i.e. if someone changes it manually, this function won't set it back to the default). We recommend always supplying an app pool name, even if it is `DefaultAppPool`. By default, the site listens on (i.e. is bound to) all IP addresses on port 80 (binding `http/*:80:`). Set custom bindings with the `Bindings` argument. Multiple bindings are allowed. Each binding must be in this format (in BNF): <PROTOCOL> '/' <IP_ADDRESS> ':' <PORT> ':' [ <HOSTNAME> ] * `PROTOCOL` is one of `http` or `https`. * `IP_ADDRESS` is a literal IP address, or `*` for all of the computer's IP addresses. This function does not validate if `IPADDRESS` is actually in use on the computer. * `PORT` is the port to listen on. * `HOSTNAME` is the website's hostname, for name-based hosting. If no hostname is being used, leave off the `HOSTNAME` part. Valid bindings are: * http/*:80: * https/10.2.3.4:443: * http/*:80:example.com ## Troubleshooting In some situations, when you add a website to an application pool that another website/application is part of, the new website will fail to load in a browser with a 500 error saying `Failed to map the path '/'.`. We've been unable to track down the root cause. The solution is to recycle the app pool, e.g. `(Get-CIisAppPool -Name 'AppPoolName').Recycle()`. .LINK Get-CIisWebsite .LINK Uninstall-CIisWebsite .EXAMPLE Install-CIisWebsite -Name 'Peanuts' -PhysicalPath C:\Peanuts.com Creates a website named `Peanuts` serving files out of the `C:\Peanuts.com` directory. The website listens on all the computer's IP addresses on port 80. .EXAMPLE Install-CIisWebsite -Name 'Peanuts' -PhysicalPath C:\Peanuts.com -Binding 'http/*:80:peanuts.com' Creates a website named `Peanuts` which uses name-based hosting to respond to all requests to any of the machine's IP addresses for the `peanuts.com` domain. .EXAMPLE Install-CIisWebsite -Name 'Peanuts' -PhysicalPath C:\Peanuts.com -AppPoolName 'PeanutsAppPool' Creates a website named `Peanuts` that runs under the `PeanutsAppPool` app pool #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.Site])] param( # The name of the website. [Parameter(Mandatory, Position=0)] [String] $Name, # The physical path (i.e. on the file system) to the website. If it doesn't exist, it will be created for you. [Parameter(Mandatory, Position=1)] [Alias('Path')] [String] $PhysicalPath, # The site's network bindings. Default is `http/*:80:`. Bindings should be specified in # `protocol/IPAddress:Port:Hostname` format. # # * Protocol should be http or https. # * IPAddress can be a literal IP address or `*`, which means all of the computer's IP addresses. This # function does not validate if `IPAddress` is actually in use on this computer. # * Leave hostname blank for non-named websites. [Parameter(Position=2)] [Alias('Bindings')] [String[]] $Binding = @('http/*:80:'), # The name of the app pool under which the website runs. The app pool must exist. If not provided, IIS picks # one for you. No whammy, no whammy! It is recommended that you create an app pool for each website. That's # what the IIS Manager does. [String] $AppPoolName, # Sets the IIS website's `id` setting. [Alias('SiteID')] [UInt32] $ID, # Sets the IIS website's `serverAutoStart` setting. [bool] $ServerAutoStart, # Return a `Microsoft.Web.Administration.Site` object for the website. [switch] $PassThru, [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30) ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $bindingRegex = '^(?<Protocol>https?):?//?(?<IPAddress>\*|[\d\.]+):(?<Port>\d+):?(?<HostName>.*)$' filter ConvertTo-Binding { param( [Parameter(ValueFromPipeline=$true,Mandatory=$true)] [string] $InputObject ) Set-StrictMode -Version 'Latest' $InputObject -match $bindingRegex | Out-Null [pscustomobject]@{ 'Protocol' = $Matches['Protocol']; 'IPAddress' = $Matches['IPAddress']; 'Port' = $Matches['Port']; 'HostName' = $Matches['HostName']; } | Add-Member -MemberType ScriptProperty ` -Name 'BindingInformation' ` -Value { '{0}:{1}:{2}' -f $this.IPAddress,$this.Port,$this.HostName } ` -PassThru } $PhysicalPath = Resolve-CFullPath -Path $PhysicalPath if( -not (Test-Path $PhysicalPath -PathType Container) ) { New-Item $PhysicalPath -ItemType Directory | Out-String | Write-Verbose } $invalidBindings = $Binding | Where-Object { $_ -notmatch $bindingRegex } if( $invalidBindings ) { $invalidBindings = $invalidBindings -join "`n`t" $errorMsg = 'The following bindings are invalid. The correct format is "protocol/IPAddress:Port:Hostname". ' + 'Protocol and IP address must be separted by a single slash, not "://". IP address can be "*" ' + 'for all IP addresses. Hostname is optional. If hostname is not provided, the binding must end ' + "with a colon.$([Environment]::NewLine)$($invalidBindings)" Write-Error $errorMsg return } [Microsoft.Web.Administration.Site] $site = $null $modified = $false if( -not (Test-CIisWebsite -Name $Name) ) { $firstBinding = $Binding | Select-Object -First 1 | ConvertTo-Binding $mgr = Get-CIisServerManager $msg = "Creating IIS website ""$($Name)"" bound to " + "$($firstBinding.Protocol)/$($firstBinding.BindingInformation)." Write-Information $msg $site = $mgr.Sites.Add( $Name, $firstBinding.Protocol, $firstBinding.BindingInformation, $PhysicalPath ) Save-CIisConfiguration } $site = Get-CIisWebsite -Name $Name if (-not $site) { return } $expectedBindings = [Collections.Generic.Hashset[String]]::New() $Binding | ConvertTo-Binding | ForEach-Object { [void]$expectedBindings.Add( ('{0}/{1}' -f $_.Protocol,$_.BindingInformation) ) } $bindingsToRemove = $site.Bindings | Where-Object { -not $expectedBindings.Contains( ('{0}/{1}' -f $_.Protocol,$_.BindingInformation ) ) } $bindingsPreamble = "IIS Website ""$($site.Name)"" Bindings" $bindingsPrefix = ' ' $shownBindingsPreamble = $false foreach( $bindingToRemove in $bindingsToRemove ) { if (-not $shownBindingsPreamble) { Write-Information $bindingsPreamble $shownBindingsPreamble = $true } Write-Information "${bindingsPrefix}- $($bindingToRemove.Protocol)/$($bindingToRemove.BindingInformation)" $site.Bindings.Remove( $bindingToRemove ) $modified = $true } $existingBindings = [Collections.Generic.Hashset[String]]::New() $site.Bindings | ForEach-Object { [void]$existingBindings.Add( ('{0}/{1}' -f $_.Protocol,$_.BindingInformation) ) } $bindingsToAdd = $Binding | ConvertTo-Binding | Where-Object { -not $existingBindings.Contains( ('{0}/{1}' -f $_.Protocol,$_.BindingInformation ) ) } foreach( $bindingToAdd in $bindingsToAdd ) { if (-not $shownBindingsPreamble) { Write-Information $bindingsPreamble $shownBindingsPreamble = $true } Write-Information "${bindingsPrefix}+ $($bindingToAdd.Protocol)/$($bindingToAdd.BindingInformation)" $site.Bindings.Add( $bindingToAdd.BindingInformation, $bindingToAdd.Protocol ) | Out-Null $modified = $true } [Microsoft.Web.Administration.Application] $rootApp = $null if( $site.Applications.Count -eq 0 ) { Write-Information "Adding ""$($Name)"" IIS website's default application." $rootApp = $site.Applications.Add('/', $PhysicalPath) $modified = $true } else { $rootApp = $site.Applications | Where-Object 'Path' -EQ '/' } if( $site.PhysicalPath -ne $PhysicalPath ) { Write-Information "Setting ""$($Name)"" IIS website's physical path to ""$($PhysicalPath)""." [Microsoft.Web.Administration.VirtualDirectory] $vdir = $rootApp.VirtualDirectories | Where-Object 'Path' -EQ '/' $vdir.PhysicalPath = $PhysicalPath $modified = $true } if( $AppPoolName ) { if( $rootApp.ApplicationPoolName -ne $AppPoolName ) { Write-Information "Setting ""$($Name)"" IIS website's application pool to ""$($AppPoolName)""." $rootApp.ApplicationPoolName = $AppPoolName $modified = $true } } if( $modified ) { Save-CIisConfiguration } $site = Get-CIisWebsite -Name $Name # Can't ever remove a site ID, only change it, so set the ID to the website's current value. $setArgs = @{ 'ID' = $site.ID; } foreach( $parameterName in (Get-Command -Name 'Set-CIisWebsite').Parameters.Keys ) { if( -not $PSBoundParameters.ContainsKey($parameterName) ) { continue } $setArgs[$parameterName] = $PSBoundParameters[$parameterName] } Set-CIisWebsite @setArgs -Reset # Now, wait until site is actually running. Do *not* use Start-CIisWebsite. If there are any HTTPS bindings that # don't have an assigned HTTPS certificate the start will fail. $timer = [Diagnostics.Stopwatch]::StartNew() $website = $null do { $website = Get-CIisWebsite -Name $Name if($website.State -ne 'Unknown') { break } Start-Sleep -Milliseconds 100 } while ($timer.Elapsed -lt $Timeout) if( $PassThru ) { return $website } } function Invoke-SetConfigurationAttribute { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ConfigurationElement] $ConfigurationElement, [Parameter(Mandatory)] [Alias('PSCmdlet')] [PSCmdlet] $SourceCmdlet, [Parameter(Mandatory)] [String] $Target, [hashtable] $Attribute = @{}, [String[]] $Exclude = @(), [switch] $Reset, [Parameter(Mandatory)] [ConfigurationElement] $Defaults, [switch] $AsDefaults ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $invocation = $SourceCmdlet.MyInvocation $cmd = $invocation.MyCommand $parameterSet = $cmd.ParameterSets | Where-Object 'Name' -EQ $SourceCmdlet.ParameterSetName if( -not $parameterSet ) { $parameterSet = $cmd.ParameterSets | Where-Object 'IsDefault' -EQ $true } $cmdParameters = $invocation.BoundParameters foreach( $attrName in ($ConfigurationElement.Attributes | Select-Object -ExpandProperty 'Name') ) { if( -not $cmdParameters.ContainsKey($attrName) -or $attrName -in $Exclude ) { continue } $Attribute[$attrName] = $cmdParameters[$attrName] } Set-CIisConfigurationAttribute -ConfigurationElement $ConfigurationElement ` -Attribute $Attribute ` -Target $Target ` -Exclude $Exclude ` -Reset:$Reset ` -Defaults $Defaults ` -AsDefaults:$AsDefaults } function Join-CIisPath { <# .SYNOPSIS Combines path segments into an IIS virtual/location path. .DESCRIPTION The `Join-CIisPath` function takes path segments and combines them into a single virtual/location path. You can pass the path segments as a list to the `Path` parameter, as multipe unnamed parameters, or pipe them in. The final path is normalized by removing extra slashes, relative path signifiers (e.g. `.` and `..`), and converting backward slashes to forward slashes. .EXAMPLE Join-CIisPath -Path 'SiteName', 'Virtual', 'Path' Demonstrates how to join paths together by passing an array of paths to the `Path` parameter. .EXAMPLE Join-CIisPath -Path 'SiteName' 'Virtual' 'Path' Demonstrates how to join paths together by passing each path as unnamed parameters. .EXAMPLE 'SiteName', 'Virtual', 'Path' | Join-CIisPath Demonstrates how to join paths together by piping each path into the function. .EXAMPLE 'SiteName', 'Virtual', 'Path' | Join-CIisPath -NoLeadingSlash Demonstrates how to omit the leading slash on the returned virtual/location path by using the `NoLeadingSlash` switch. #> [CmdletBinding()] param( # The parent path. [Parameter(Mandatory, Position=0, ValueFromPipeline)] [AllowEmptyString()] [AllowNull()] [String[]]$Path, # All remaining arguments are passed to this parameter. Each path passed are also appended to the path. This # parameter exists to allow you to call `Join-CIisPath` with each path to join as a positional parameter, e.g. # `Join-Path -Path 'one' 'two' 'three' 'four' 'five' 'six'`. [Parameter(Position=1, ValueFromRemainingArguments)] [String[]] $ChildPath, # If set, the returned virtual path will have a leading slash. The default behavior is for the returned path # not to have a leading slash. [switch] $LeadingSlash ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $segments = [Collections.Generic.List[String]]::New() } process { if (-not $Path) { return } foreach ($pathItem in $Path) { if (-not $pathItem) { continue } $segments.Add($pathItem) } } end { $fullPath = (& { if ($segments.Count) { $segments | Write-Output } if ($ChildPath) { $ChildPath | Where-Object { $_ } | Write-Output } }) -join '/' return $fullPath | ConvertTo-CIisVirtualPath -NoLeadingSlash:(-not $LeadingSlash) } } function Join-CIisVirtualPath { <# .SYNOPSIS OBSOLETE. Use `Join-CIisPath` instead. .DESCRIPTION OBSOLETE. Use `Join-CIisPath` instead. #> [CmdletBinding()] param( # The parent path. [Parameter(Mandatory, Position=0)] [AllowEmptyString()] [AllowNull()] [String]$Path, # [Parameter(Position=1)] [String[]] $ChildPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $msg = 'The "Join-CIisVirtualPath" function is OBSOLETE and will be removed in the next major version of ' + 'Carbon.IIS. Please use the `Join-CIisPath` function instead.' Write-CIisWarningOnce -Message $msg if( $ChildPath ) { $Path = Join-Path -Path $Path -ChildPath $ChildPath } $Path.Replace('\', '/').Trim('/') } function Lock-CIisConfigurationSection { <# .SYNOPSIS Locks an IIS configuration section so that it can't be modified/overridden by individual websites. .DESCRIPTION Locks configuration sections globally so they can't be modified by individual websites. For a list of section paths, run C:\Windows\System32\inetsrv\appcmd.exe lock config /section:? Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Lock-CIisConfigurationSection -SectionPath 'system.webServer/security/authentication/basicAuthentication' Locks the `basicAuthentication` configuration so that sites can't override/modify those settings. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( # The path to the section to lock. For a list of sections, run # # C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:? [Parameter(Mandatory)] [String[]] $SectionPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState foreach( $sectionPathItem in $SectionPath ) { $section = Get-CIisConfigurationSection -SectionPath $sectionPathItem $section.OverrideMode = 'Deny' Save-CIisConfiguration -Target $sectionPathItem -Action 'Locking IIS Configuration Section' } } function Remove-CIisCollectionItem { <# .SYNOPSIS Removes a IIS configuration element. .DESCRIPTION The `Remove-CIisCollectionItem` function removes an item from an IIS configuration collection. Pass the collection's IIS configuration section path to the `SectionPath` parameter and the value to remove from the collection to the `Value` parameter. This function removes that value from the collection if it exists. If the value does not exist, the function writes an error. If removing an item from the collection for a website, application, virtual directory, pass the path to that location to the `LocationPath` parameter' .EXAMPLE Remove-CIisCollectionItem -SectionPath 'system.webServer/httpProtocol' -CollectionName 'customHeaders' -Value 'X-CarbonRemoveItem' Demonstrates how to remove the 'X-CarbonRemoveItem' header if it has previously been added. .EXAMPLE Remove-CIisCollectionItem -LocationPath 'SITE_NAME' -SectionPath `system.webServer/httpProtocol' -CollectionName 'customHeaders' -Value 'X-CarbonRemoveItem' Demonstrates how to remove the 'X-CarbonRemoveItem' header from the 'SITE_NAME' location. #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The `[Microsoft.Web.Administration.ConfigurationElement]` object to get as a collection or the parent element # of the collection element to get. If this is the parent element, pass the name of the child element collection # to the `CollectionName` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The path to the collection's configuration section. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The location path of the site, directory, application, or virtual directory whose configuration to update. # Default is to update the global configuration. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The collection the item belongs to. [Alias('Name')] [String] $CollectionName, # The value to be removed. [Parameter(Mandatory, ValueFromPipeline)] [String[]] $Value, # The attribute name for the attribute that uniquely identifies each item in a collection. This is usually # automatically detected. [String] $UniqueKeyAttributeName, # ***INTERNAL***. Do not use. [switch] $SkipCommit ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($CollectionName) { $getArgs['Name'] = $CollectionName } $displayPath = '' if ($ConfigurationElement) { $getArgs['ConfigurationElement'] = $ConfigurationElement $displayPath = $ConfigurationElement.ElementTagName } else { $getArgs['SectionPath'] = $SectionPath if ($LocationPath) { $getArgs['LocationPath'] = $LocationPath } $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath -LocationPath $LocationPath -SubSectionPath $CollectionName } $stopProcessing = $false $collection = Get-CIisCollection @getArgs if (-not $collection) { $stopProcessing = $true return } if (-not $UniqueKeyAttributeName) { $UniqueKeyAttributeName = Get-CIisCollectionKeyName -Collection $collection if (-not $UniqueKeyAttributeName) { $stopProcessing = $true $msg = "Failed to remove items from IIS configuration collection ${displayPath} because that " + 'collection doesn''t have a unique key attribute. Use the "UniqueKeyAttributeName" parameter ' + 'to specify the attribute name.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } } $firstLine = "IIS configuration collection ${displayPath}" $firstLineWritten = $false $itemsRemoved = $false } process { if ($stopProcessing) { return } foreach ($valueItem in $Value) { $itemToRemove = $collection | Where-Object { $_.GetAttributeValue($UniqueKeyAttributeName) -eq $valueItem } if (-not $itemToRemove) { $msg = "Failed to remove item ""${valueItem}"" from IIS configuration collection ${displayPath} " + 'because it doesn''t exist in the collection.' Write-Error $msg -ErrorAction $ErrorActionPreference return } if (-not $firstLineWritten) { Write-Information $firstLine $firstLineWritten = $true } Write-Information " - $($valueItem)" $collection.Remove($itemToRemove) $itemsRemoved = $true } } end { if ($stopProcessing -or -not $itemsRemoved) { return } if ($SkipCommit) { return $true } Save-CIisConfiguration } } function Remove-CIisConfigurationAttribute { <# .SYNOPSIS Removes an attribute from a configuration section. .DESCRIPTION The `Remove-CIisConfigurationAttribute` function removes an attribute from a configuration section in the IIS application host configuration file. Pass the configuration section path to the `SectionPath` parameter, and the names of the attributes to remove to the `Name` parameter (or pipe the names to `Remove-CIisConfigurationAttribute`). The function deletes that attribute. If the attribute doesn't exist, nothing happens. To delete/remove an attribute from the configuration of an application/virtual directory under a website, pass the application/virtual diretory's name/path to the `VirtualPath` parameter. To remove an attribute from an arbitrary configuration element, pass the configuration element to the `ConfigurationElement` parameter. You must also pass the xpath to that element to the `ElementXpath` parameter because the Microsoft.Web.Administration API doesn't expose a way to determine if an attribute no longer exists in the applicationHost.config file, so `Remove-CIisConfigurationAttribute` has to check. .EXAMPLE Remove-CIisConfigurationAttribute -SiteName 'MySite' -SectionPath 'system.webServer/security/authentication/anonymousAuthentication' -Name 'userName' Demonstrates how to delete/remove the attribute from a website's configuration. In this example, the `userName` attribute on the `system.webServer/security/authentication/anonymousAuthentication` configuration is deleted. .EXAMPLE Remove-CIisConfigurationAttribute -SiteName 'MySite' -VirtualPath 'myapp/appdir' -SectionPath 'system.webServer/security/authentication/anonymousAuthentication' -Name 'userName' Demonstrates how to delete/remove the attribute from a website's path/application/virtual directory configuration. In this example, the `userName` attribute on the `system.webServer/security/authentication/anonymousAuthentication` for the '/myapp/appdir` directory is removed. .EXAMPLE Remove-CIisConfigurationAttribute -ConfigurationElement $vdir -ElementXpath "system.applicationHost/sites/site[@name = 'site']/application[@path = '/']/virtualDirectory[@path = '/']" -Name 'logonMethod' Demonstrates how to remove an attribute from an arbitrary configuration element. In this example, the `logonMethod` attribute will be removed from the `site` site's default virtual directory. #> [CmdletBinding(SupportsShouldProcess)] param( # The configuration element whose attribute to remove. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The xpath expression to the configuration element in the applicationHost.config file, without the # `/configuration` root path. The Microsoft.Web.Administration API doesn't expose a way to check if an attribute # is defined or is missing and has its default value. This xpath expression is used to check if an attribute # exists or not. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [String] $ElementXpath, # The name of the website to configure. [Parameter(Mandatory, Position=0, ParameterSetName='BySectionPath')] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Parameter(ParameterSetName='BySectionPath')] [String] $VirtualPath = '', # The configuration section path to configure, e.g. # `system.webServer/security/authentication/basicAuthentication`. The path should *not* start with a forward # slash. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The name of the attribute to remove/clear. If the attribute doesn't exist, nothing happens. # # You can pipe multiple names to clear/remove multiple attributes. [Parameter(Mandatory, ValueFromPipeline)] [Alias('Key')] [String[]] $Name ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $desc = '' if ($ConfigurationElement) { $desc = Get-CIisDescription -ConfigurationElement $ConfigurationElement } else { $ConfigurationElement = Get-CIisConfigurationSection -SectionPath $SectionPath ` -LocationPath $LocationPath ` -VirtualPath $VirtualPath if( -not $ConfigurationElement ) { return } $desc = Get-CIisDescription -SectionPath $SectionPath -LocationPath $LocationPath } $attrNameFieldLength = $ConfigurationElement.Attributes | Select-Object -ExpandProperty 'Name' | Select-Object -ExpandProperty 'Length' | Measure-Object -Maximum | Select-Object -ExpandProperty 'Maximum' $nameFormat = "{0,-$($attrNameFieldLength)}" $save = $false } process { if( -not $ConfigurationElement ) { return } $shownDescription = $false Write-Debug $desc foreach( $nameItem in $Name ) { $attr = $ConfigurationElement.Attributes[$nameItem] if( -not $attr ) { $msg = "${desc} doesn't have a ""${nameItem}"" attribute." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $nameItem = "$($nameItem.Substring(0, 1).ToLowerInvariant())$($nameItem.Substring(1, $nameItem.Length -1))" $msg = " $($nameFormat -f $nameItem)] $($attr.IsInheritedFromDefaultValue) $($attr.Value) " + "$($attr.Schema.DefaultValue)" Write-Debug $msg $exists = $false if ($PSCmdlet.ParameterSetName -eq 'ByConfigurationElement') { $exists = Test-CIisApplicationHostElement -Xpath "${ElementXpath}/@${nameItem}" } else { $exists = Test-CIisApplicationHostElement -Xpath "${SectionPath}/@${nameItem}" -LocationPath $LocationPath } if (-not $exists) { Write-Verbose "Attribute ${nameItem} on ${desc} does not exist." continue } $target = "$($nameItem) on ${desc}" $action = 'Remove Attribute' if( $PSCmdlet.ShouldProcess($target, $action) ) { if (-not $shownDescription) { Write-Information $desc $shownDescription = $true } Write-Information " - ${nameItem}" $attr.Delete() $save = $true } } } end { if (-not $ConfigurationElement) { return } if (-not $save) { return } Save-CiisConfiguration } } function Remove-CIisConfigurationLocation { <# .SYNOPSIS Removes a <location> element from applicationHost.config. .DESCRIPTION The `Remove-CIisConfigurationLocation` function removes the entire location configuration for a website or a path under a website. When configuration for a website or path under a website is made, those changes are sometimes persisted to IIS's applicationHost.config file. The configuration is placed inside a `<location>` element for that site and path. This function removes the entire `<location>` section, i.e. all a site's/path's custom configuration that isn't stored in a web.config file. Pass the website whose location configuration to remove to the `LocationPath` parameter. To delete the location configuration for a path under the website, pass that path to the `VirtualPath` parameter. If there is no location configuration, an error is written. .EXAMPLE Remove-CIisConfigurationLocation -LocationPath 'www' Demonstrates how to remove the `<location path="www">` element from IIS's applicationHost.config, i.e. all custom configuration for the www website that isn't in the site's web.config file. .EXAMPLE Remove-CIisConfigurationLocation -LocationPath 'www/some/path' Demonstrates how to remove the `<location path="www/some/path">` element from IIS's applicationHost.config, i.e. all custom configuration for the `some/path` path in the `www` website that isn't in the path's or site's web.config file. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [String] $VirtualPath, [String] $SectionPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { $LocationPath = Join-CIisPath -Path $LocationPath, $VirtualPath } if (-not (Get-CIisConfigurationLocationPath -LocationPath $LocationPath)) { $msg = "IIS configuration location ""${LocationPath}"" does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } if (-not $SectionPath) { (Get-CIisServerManager).GetApplicationHostConfiguration().RemoveLocationPath($LocationPath) $target = "$($LocationPath)" $action = "Remove IIS Location" $infoMsg = "Removing ""$($LocationPath)"" IIS location configuration." Save-CIisConfiguration -Target $target -Action $action -Message $infoMsg return } $section = Get-CIisConfigurationSection -LocationPath $LocationPath -SectionPath $SectionPath if (-not $section) { return } $desc = Get-CIisDescription -ConfigurationElement $section if (-not (Test-CIisApplicationHostElement -XPath $section.SectionPath -LocationPath $section.LocationPath)) { $msg = "Failed to delete ${desc} because that configuration section does not exist for that location." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $msg = "Removing ${desc}." Write-Information $msg $section.Delete() Save-CIisConfiguration } function Remove-CIisMimeMap { <# .SYNOPSIS Removes a file extension to MIME type map from an entire web server. .DESCRIPTION IIS won't serve static files unless they have an entry in the MIME map. Use this function to remove an existing MIME map entry. If one doesn't exist, nothing happens. Not even an error. If a specific website has the file extension in its MIME map, that site will continue to serve files with those extensions. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .LINK Get-CIisMimeMap .LINK Set-CIisMimeMap .EXAMPLE Remove-CIisMimeMap -FileExtension '.m4v' -MimeType 'video/x-m4v' Removes the `.m4v` file extension so that IIS will no longer serve those files. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ForWebServer')] param( # The name of the website whose MIME type to set. [Parameter(Mandatory, ParameterSetName='ForWebsite', Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Uset the `LocationPath` parameter instead. [Parameter(ParameterSetName='ForWebsite')] [String] $VirtualPath = '', # The file extension whose MIME map to remove. [Parameter(Mandatory)] [String] $FileExtension ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getIisConfigSectionParams = @{ } if( $PSCmdlet.ParameterSetName -eq 'ForWebsite' ) { $getIisConfigSectionParams['LocationPath'] = $LocationPath $getIisConfigSectionParams['VirtualPath'] = $VirtualPath } $staticContent = Get-CIisConfigurationSection -SectionPath 'system.webServer/staticContent' @getIisConfigSectionParams $mimeMapCollection = $staticContent.GetCollection() $mimeMapToRemove = $mimeMapCollection | Where-Object { $_['fileExtension'] -eq $FileExtension } if( -not $mimeMapToRemove ) { Write-Verbose ('MIME map for file extension {0} not found.' -f $FileExtension) return } $mimeMapCollection.Remove( $mimeMapToRemove ) Save-CIisConfiguration } function Rename-CIisAppPool { <# .SYNOPSIS Renames an IIS application pool. .DESCRIPTION The `Rename-CIisAppPool` function renames an IIS application pool. Pass the name of the application pool to the `Name` parameter. Wildcards are permitted if the pattern only matches one application pool. Pass the new name of the application pool to the `NewName` parameter. If the application pool does not exist, the function writes an error and does nothing. If the application pool is assigned to any websites or applications, the rename will fail with an error message. IIS doesn't support renaming application pools that are assigned to any website or application. .EXAMPLE Rename-CIisAppPool -Name 'OldName' -NewName 'NewName' Demonstrates how to rename an IIS application with name "OldName" to "NewName". #> [CmdletBinding()] param( # The name of the appliciton pool to rename. [Parameter(Mandatory)] [String] $Name, # The website's new name. [Parameter(Mandatory)] [String] $NewName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPool = Get-CIisAppPool -Name $Name if (-not $appPool) { return } $appPoolCount = ($appPool | Measure-Object).Count if ($appPoolCount -gt 1) { $msg = "Failed to rename application pool ""${Name}"" because there are ${appPoolCount} application pools " + 'that match that name.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $appCount = Get-CIisApplication | Where-Object 'ApplicationPoolName' -EQ $Name | Measure-Object | Select-Object -ExpandProperty 'Count' if ($appCount -gt 0) { $suffix = '' if ($appCount -gt 1) { $suffix = 's' } $msg = "Failed to rename application pool ""${Name}"" because it is assigned to ${appCount} " + "application${suffix}." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $appPool.Name = $NewName Save-CIisConfiguration } function Rename-CIisWebsite { <# .SYNOPSIS Renames an IIS website. .DESCRIPTION The `Rename-CIisWebsite` function renames an IIS website. Pass the name of the website to the `Name` parameter. Wildcards are permitted if the pattern only matches one website. Pass the new name of the website to the `NewName` parameter. If the website does not exist, the function writes an error and does nothing. .EXAMPLE Rename-CIisWebsite -Name 'OldName' -NewName 'NewName' Demonstrates how to rename an IIS application with name "OldName" to "NewName". #> [CmdletBinding()] param( # The name of the website to rename. [Parameter(Mandatory)] [String] $Name, # The website's new name. [Parameter(Mandatory)] [String] $NewName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $Name if (-not $site) { return } $siteCount = ($site | Measure-Object).Count if ($siteCount -gt 1) { $msg = "Failed to rename website ""${Name}"" because there are ${siteCount} websites that match that name." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $site.Name = $NewName Save-CIisConfiguration } function Restart-CIisAppPool { <# .SYNOPSIS Restarts an IIS application pool. .DESCRIPTION The `Restart-CIisAppPool` restarts an IIS application pool. Pass the names of the application pools to restart to the `Name` parameter. You can also pipe application pool objects or application pool names. The application pool is stopped then started. If stopping the application pool fails, the function does not attempt to start it. If after 30 seconds, the application pool hasn't stopped, the function writes an error, and returns; it does not attempt to start the application pool. Use the `Timeout` parameter to control how long to wait for the application pool to stop. When the application pool hasn't stopped, and the `Force` parameter is true, the function attempts to kill all of the application pool's worker processes, again waiting for `Timeout` interval for the processes to exit. If the function is unable to kill the worker processes, the function will write an error. .EXAMPLE Restart-CIisAppPool -Name 'Default App Pool' Demonstrates how to restart an application pool by passing its name to the `Name` parameter. .EXAMPLE Restart-CIisAppPool -Name 'Default App Pool', 'Non-default App Pool' Demonstrates how to restart multiple application pools by passing their names to the `Name` parameter. .EXAMPLE Get-CIisAppPool | Restart-CIisAppPool Demonstrates how to restart an application pool by piping it to `Restart-CIisAppPool`. .EXAMPLE 'Default App Pool', 'Non-default App Pool' | Restart-CIisAppPool Demonstrates how to restart one or more application pools by piping their names to `Restart-CIisAppPool`. .EXAMPLE Restart-CIisAppPool -Name 'Default App Pool' -Timeout '00:00:10' Demonstrates how to change the amount of time `Restart-CIisAppPool` waits for the application pool to stop. In this example, it will wait 10 seconds. .EXAMPLE Restart-CIisAppPool -Name 'Default App Pool' -Force Demonstrates how to stop an application pool that won't stop by using the `Force` (switch). After waiting for the application pool to stop, if it is still running and the `Force` (switch) is used, `Restart-CIisAppPool` will try to kill the application pool's worker processes. #> [CmdletBinding()] param( # One or more names of the application pools to restart. You can also pipe one or more names to the function or # pipe one or more application pool objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Restart-CIisAppPool` waits for an application pool to stop before giving up and writing # an error. The default is 30 seconds. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30), # If set, and an application pool fails to stop on its own, `Restart-CIisAppPool` will attempt to kill the # application pool worker processes. [switch] $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $stopErrors = @() Stop-CIisAppPool -Name $Name -Timeout $Timeout -Force:$Force -ErrorVariable 'stopErrors' if ($stopErrors) { return } Start-CIisAppPool -Name $Name -Timeout $Timeout } } function Restart-CIisWebsite { <# .SYNOPSIS Restarts an IIS website. .DESCRIPTION The `Restart-CIisWebsite` restarts an IIS website. Pass the names of the websites to restart to the `Name` parameter. You can also pipe website objects or website names. The website is stopped then started. If stopping the website fails, the function does not attempt to start it. If after 30 seconds, the website hasn't stopped, the function writes an error, and returns; it does not attempt to start the website. Use the `Timeout` parameter to control how long to wait for the website to stop. The function writes an error if the website doesn't stop or start. .EXAMPLE Restart-CIisWebsite -Name 'Default Website' Demonstrates how to restart an website by passing its name to the `Name` parameter. .EXAMPLE Restart-CIisWebsite -Name 'Default Website', 'Non-default Website' Demonstrates how to restart multiple websites by passing their names to the `Name` parameter. .EXAMPLE Get-CIisWebsite | Restart-CIisWebsite Demonstrates how to restart an website by piping it to `Restart-CIisWebsite`. .EXAMPLE 'Default Website', 'Non-default Website' | Restart-CIisWebsite Demonstrates how to restart one or more websites by piping their names to `Restart-CIisWebsite`. .EXAMPLE Restart-CIisWebsite -Name 'Default Website' -Timeout '00:00:10' Demonstrates how to change the amount of time `Restart-CIisWebsite` waits for the website to stop. In this example, it will wait 10 seconds. #> [CmdletBinding()] param( # One or more names of the websites to restart. You can also pipe one or more names to the function or # pipe one or more website objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Restart-CIisWebsite` waits for an website to stop before giving up and writing # an error. The default is 30 seconds. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30) ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $stopErrors = @() Stop-CIisWebsite -Name $Name -Timeout $Timeout -ErrorVariable 'stopErrors' if ($stopErrors) { return } Start-CIisWebsite -Name $Name -Timeout $Timeout } } function Resume-CIisAutoCommit { <# .SYNOPSIS Starts Carbon.IIS functions committing changes to IIS. .DESCRIPTION The `Resume-CIisAutoCommit` functions starts Carbon.IIS functions committing changes to IIS. Some IIS configuration is only committed correctly when an item is first saved/created. To ensure that all the changes made by Carbon.IIS are committed at the same time, call `Suspend-CIisAutoCommit`, make your changes, then call `Resume-CIisAutoCommit -Save` to start auto-committing again *and* to commit all uncomitted changes. .EXAMPLE Resume-CIisAutoCommit -Save Demonstrates how to call this function to both start auto-committing changes again *and* to save any uncommitted changes. .EXAMPLE Resume-CIisAutoCommit Demonstrates how to call this function to start auto-committing changes but not to save any uncommitted changes and leave them pending in memory. #> [CmdletBinding()] param( # When set, will save any uncommitted changes. [switch] $Save ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $script:skipCommit = $false if (-not $Save) { return } Save-CIisConfiguration } function Save-CIisConfiguration { <# .SYNOPSIS Saves configuration changes to IIS. .DESCRIPTION The `Save-CIisConfiguration` function saves changes made by Carbon.IIS functions or changes made on any object returned by any Carbon.IIS function. After making those changes, you must call `Save-CIisConfiguration` to save those changes to IIS. Carbon.IIS keeps an internal `Microsoft.Web.Administration.ServerManager` object that it uses to get all objects it operates on or returns to the user. `Save-CIisConfiguration` calls the `CommitChanges()` method on that Server Manager object. .EXAMPLE Save-CIIsConfiguration Demonstrates how to use this function. #> [CmdletBinding(SupportsShouldProcess)] param( # Optional target object descripotion whose configuration will end up being saved. This is used as the target # when `-WhatIf` is true and calling `ShouldProcess(string target, string action)`. [String] $Target, # Optional action description to use when `-WhatIf` is used and calling # `ShouldProcess(string target, string action)`. Only used if `Target` is given. [String] $Action, # Optional message written to the information stream just before saving changes. [String] $Message ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $WhatIfPreference ) { if( $Target ) { $Target = $Target -replace '"', '''' } if( $Target -and $Action ) { $PSCmdlet.ShouldProcess($Target, $Action) | Out-Null } if( $Target ) { $PSCmdlet.ShouldProcess($Target) | Out-Null } Get-CIisServerManager -Reset | Out-Null return } if( $Message ) { Write-Information $Message } Get-CIisServerManager -Commit | Out-Null } function Set-CIisAnonymousAuthentication { <# .SYNOPSIS Configures anonymous authentication for all or part of a website. .DESCRIPTION The `Set-CIisAnonymousAuthentication` function configures anonymous authentication for all or part of a website. Pass the name of the site to the `SiteName` parameter. To enable anonymous authentication, use the `Enabled` switch. To set the identity to use for anonymous access, pass the identity's username to the `UserName` and password to the `Pasword` parameters. To set the logon method for the anonymous user, use the `LogonMethod` parameter. To configure anonymous authentication on a path/application/virtual directory under a website, pass the virtual path to that path/application/virtual directory to the `VirtualPath` parameter. .EXAMPLE Set-CIisAnonymousAuthentication -SiteName 'MySite' -Enabled -UserName 'MY_IUSR' -Password $password -LogonMethod Interactive Demonstrates how to use `Set-CIisAnonymousAuthentication` to configure all attributes of anonymous authentication: it is enabled with the `Enabled` switch, the idenity of anonymous access is set to `MY_IUSR` whose password is $password, with a logon method of `Interactive`. .EXAMPLE Set-CIisAnonymousAuthentication -SiteName 'MySite' -VirtualPath 'allowAll' -Enabled Demonstrates how to use `Set-CIisAnonymousAuthentication` to configure anonymous authentication on a path/application/virtual directry under a site. In this example, anonymous authentication is enabled in the `MySite` website's `allowAll` path/application/virtual directory. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The name of the website whose anonymous authentication settings to change. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [String] $VirtualPath, # Enable anonymous authentication. To disable anonymous authentication you must explicitly set `Enabled` to # `$false`, e.g. `-Enabled $false`. [bool] $Enabled, # The logon method to use for anonymous access. [AuthenticationLogonMethod] $LogonMethod, # The password username of the identity to use to run anonymous requests. Not needed if using system accounts. [SecureString] $Password, # The username of the identity to use to run anonymous requests. [String] $UserName, # If set, the anonymous authentication setting for each parameter *not* passed is deleted, which resets it to # its default value. Otherwise, anonymous authentication settings whose parameters are not passed are left in # place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } $attributes = $PSBoundParameters | Copy-Hashtable -Key @('enabled', 'logonMethod', 'password', 'userName') Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath 'system.webServer/security/authentication/anonymousAuthentication' ` -Attribute $attributes ` -Reset:$Reset } function Set-CIisAppPool { <# .SYNOPSIS Configures an IIS application pool's settings. .DESCRIPTION The `Set-CIisAppPool` function configures an IIS application pool's settings. Pass the name of the application pool to the `Name` parameter. Pass the configuration you want to one or more of the AutoStart, CLRConfigFile, Enable32BitAppOnWin64, EnableConfigurationOverride, ManagedPipelineMode, ManagedRuntimeLoader, ManagedRuntimeVersion, Name, PassAnonymousToken, QueueLength, and/or StartMode parameters. See [Adding Application Pools <add>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/) for documentation on each setting. You can configure the IIS application pool defaults instead of a specific application pool by using the `AsDefaults` switch. If you want to ensure that any settings that may have gotten changed by hand are reset to their default values, use the `-Reset` switch. When set, the `-Reset` switch will reset each setting not passed as an argument to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/ .EXAMPLE Set-CIisAppPool -AppPoolName 'ExampleTwo' -Enable32BitAppOnWin64 $true -ManagedPipelineMode Classic Demonstrates how to configure an IIS application pool's settings. In this example, the app pool will be updated to run as a 32-bit applicaiton and will use a classic pipeline mode. All other settings are left unchanged. .EXAMPLE Set-CIisAppPool -AppPoolName 'ExampleOne' -Enable32BitAppOnWin64 $true -ManagedPipelineMode Classic -Reset Demonstrates how to reset an IIS application pool's settings to their default values by using the `-Reset` switch. In this example, the `enable32BitAppOnWin64` and `managedPipelineMode` settings are set to `true` and `Classic`, and all other application pool settings are deleted, which reset them to their default values. .EXAMPLE Set-CIisAppPool -AsDefaults -Enable32BitAppOnWin64 $true -ManagedPipelineMode Classic Demonstrates how to configure the IIS application pool defaults settings by using the `AsDefaults` switch and not passing application pool name. In this case, all future application pools created will be 32-bit applications and use a classic pipeline mode, unless those settings are configured differently upon install. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the application pool whose settings to configure. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $Name, # If true, the function configures the IIS application pool defaults instead of a specific application pool. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS application pool's `autoStart` setting. [bool] $AutoStart, # Sets the IIS application pool's `CLRConfigFile` setting. [String] $CLRConfigFile, # Sets the IIS application pool's `enable32BitAppOnWin64` setting. [bool] $Enable32BitAppOnWin64, # Sets the IIS application pool's `enableConfigurationOverride` setting. [bool] $EnableConfigurationOverride, # Sets the IIS application pool's `managedPipelineMode` setting. [ManagedPipelineMode] $ManagedPipelineMode, # Sets the IIS application pool's `managedRuntimeLoader` setting. [String] $ManagedRuntimeLoader, # Sets the IIS application pool's `managedRuntimeVersion` setting. [String] $ManagedRuntimeVersion, # Sets the IIS application pool's `passAnonymousToken` setting. [bool] $PassAnonymousToken, # Sets the IIS application pool's `queueLength` setting. [UInt32] $QueueLength, # Sets the IIS application pool's `startMode` setting. [StartMode] $StartMode, # If set, the application pool setting for each parameter *not* passed is deleted, which resets it to its # default value. Otherwise, application pool settings whose parameters are not passed are left in place and not # modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($Name) { $getArgs['Name'] = $Name } elseif ($AsDefaults) { $getArgs['Defaults'] = $true } $target = Get-CIisAppPool @getArgs if( -not $target ) { return } $targetMsg = 'IIS application pool defaults' if( $Name ) { $targetMsg = "IIS application pool ""$($Name)""" } Invoke-SetConfigurationAttribute -ConfigurationElement $target ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Exclude @('applicationPoolSid', 'state') ` -Reset:$Reset ` -Defaults (Get-CIIsAppPool -Defaults) ` -AsDefaults:$AsDefaults } function Set-CIisAppPoolCpu { <# .SYNOPSIS Configures IIS application pool CPU settings. .DESCRIPTION The `Set-CIisAppPoolCpu` configures an IIS application pool's CPU settings. Pass the application pool's name to the `AppPoolName` parameter. With no other parameters, the `Set-CIisAppPoolCpu` function removes all configuration from that application pool's CPU, which resets them to their defaults. To change a setting to a non-default value, pass the new value to its corresponding parameter. For each parameter that is *not* passed, its corresponding configuration is removed, which reset that configuration to its default value. See [CPU Settings for an Application Pool <cpu>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/cpu) for documentation on each setting. You can configure IIS's application pool defaults instead of a specific application pool's settings by using the `AsDefaults` switch. If you want to ensure that any settings that may have gotten changed by hand are reset to their default values, use the `-Reset` switch. When set, the `-Reset` switch will reset each setting not passed as an argument to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/cpu .EXAMPLE Set-CIisAppPoolCpu -AppPoolName -DefaultAppPool -Limit 50000 -Action Throttle Demonstrates how to customize some of an application pool's CPU settings, while resetting all other configuration to their default values. In this example, the `limit` and `action` settings are set, and all other settings are removed, which resets them to their default values. .EXAMPLE Set-CIisAppPoolCpu -AppPoolName 'DefaultAppPool' -Limit 50000 -Action Throttle -Reset Demonstrates how to set *all* an IIS application pool's CPU settings by using the `-Reset` switch. In this example, the `limit` and `throttle` settings are set to custom values, and all other settings are deleted, which resets them to their default values. .EXAMPLE Set-CIisAppPool -AsDefaults -Limit 50000 -ActionThrottle Demonstrates how to configure the IIS application pool defaults CPU settings by using the `-AsDefaults` switch and not passing an application pool name. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $AppPoolName, # If true, the function configures IIS' application pool defaults instead of [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # The value for the application pool's `action` CPU setting. [ProcessorAction] $Action, # The value for the application pool's `limit` CPU setting. [UInt32] $Limit, # The value for the application pool's `numaNodeAffinityMode` CPU setting. [CIisNumaNodeAffinityMode] $NumaNodeAffinityMode, # The value for the application pool's `numaNodeAssignment` CPU setting. [CIisNumaNodeAssignment] $NumaNodeAssignment, # The value for the application pool's `processorGroup` CPU setting. [int] $ProcessorGroup, # The value for the application pool's `resetInterval` CPU setting. [TimeSpan] $ResetInterval, # The value for the application pool's `smpAffinitized` CPU setting. [bool] $SmpAffinitized, # The value for the application pool's `smpProcessorAffinityMask` CPU setting. [UInt32] $SmpProcessorAffinityMask, # The value for the application pool's `smpProcessorAffinityMask2` CPU setting. [UInt32] $SmpProcessorAffinityMask2, # If set, the application pool CPU setting for each parameter *not* passed is deleted, which resets it to its # default value. Otherwise, application pool CPU settings whose parameters are not passed are left in place and # not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($AsDefaults) { $getArgs['Defaults'] = $true } elseif ($AppPoolName) { $getArgs['Name'] = $AppPoolName } $appPool = Get-CIisAppPool @getArgs if( -not $appPool ) { return } $target = 'IIS application pool defaults CPU' if( $AppPoolName ) { $target = """$($AppPoolName)"" IIS application pool's CPU" } Invoke-SetConfigurationAttribute -ConfigurationElement $appPool.Cpu ` -PSCmdlet $PSCmdlet ` -Target $target ` -Reset:$Reset ` -Defaults (Get-CIIsAppPool -Defaults).Cpu ` -AsDefaults:$AsDefaults } function Set-CIisAppPoolPeriodicRestart { <# .SYNOPSIS Configures an IIS application pool's periodic restart settings. .DESCRIPTION The `Set-CIisAppPoolPeriodicRestart` function configures all the settings on an IIS application pool's periodic restart settings. Pass the name of the application pool to the `AppPoolName` parameter. Pass the configuration to the `Memory`, `PrivateMemory`, `Requests`, and `Time` parameters (see [Periodic Restart Settings for Application Pool Recycling <periodicRestart>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/periodicrestart/)) for documentation on what these settings are for. Use the `Schedule` parameter to add times to the periodic restart configuration for time each day IIS should recycle the application pool. If you want to ensure that any settings that may have gotten changed by hand are reset to their default values, use the `-Reset` switch. When set, the `-Reset` switch will reset each setting not passed as an argument to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/periodicrestart/ .EXAMPLE Set-CIisAppPoolPeriodicRestart -AppPoolName 'Snafu' -Memory 1000000 -PrivateMemory 2000000 -Requests 3000000 -Time '23:00:00' Demonstrates how to configure all an IIS applicaton pool's periodic restart settings. In this example, `memory` will be set to `1000000`, `privateMemory` will be set to `2000000`, `requests` will be sent to `3000000`, and `time` will be sent to `23:00:00'. .EXAMPLE Set-CIisAppPoolPeriodicRestart -AppPoolName 'Fubar' -Memory 1000000 -PrivateMemory 2000000 -Reset Demonstrates how to set *all* an IIS application pool's periodic restart settings by using the `-Reset` switch. Any setting not passed will be deleted, which resets it to its default value. In this example, the `memory` and `privateMemory` settings are configured, and all other settings are set to their default values. .EXAMPLE Set-CIisAppPoolPeriodicRestart -AsDefaults -Memory 1000000 -PrivateMemory 2000000 Demonstrates how to configure the IIS application pool defaults periodic restart settings by using the `AsDefaults` switch and not passing the application pool name. #> [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the IIS application pool whose periodic restart settings to configure. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $AppPoolName, # If true, the function configures IIS' application pool defaults instead of [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS application pool's periodic restart `memory` setting. [UInt32] $Memory, # Sets the IIS application pool's periodic restart `privateMemory` setting. [UInt32] $PrivateMemory, # Sets the IIS application pool's periodic restart `requests` setting. [UInt32] $Requests, # Sets the IIS application pool's periodic restart `time` setting. [TimeSpan] $Time, # Sets the IIS application pool's periodic restart `schedule` list. The default is to have no scheduled # restarts. [TimeSpan[]] $Schedule = @(), # If set, the application pool periodic restart setting for each parameter *not* passed is deleted, which resets # it to its default value. Otherwise, application pool periodic restart settings whose parameters are not passed # are left in place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($AppPoolName) { $getArgs['Name'] = $AppPoolName } elseif ($AsDefaults) { $getArgs['Defaults'] = $true $AppPoolName = 'default' } $appPool = Get-CIisAppPool @getArgs if( -not $appPool ) { return } $targetMsg = """$($AppPoolName)"" IIS application pool periodic restart" Invoke-SetConfigurationAttribute -ConfigurationElement $appPool.Recycling.PeriodicRestart ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset ` -Defaults (Get-CIIsAppPool -Defaults).Recycling.PeriodicRestart ` -AsDefaults:$AsDefaults $appPool = Get-CIisAppPool @getArgs if( -not $appPool ) { return } $currentSchedule = $appPool.Recycling.PeriodicRestart.Schedule $currentTimes = $currentSchedule | Select-Object -ExpandProperty 'Time' | Sort-Object $currentTimesMsg = $currentTimes -join ', ' $Schedule = $Schedule | Sort-Object $scheduleMsg = $Schedule -join ', ' $scheduleChanged = $false Write-Debug "$($AppPoolName) application pool recycling/periodicRestart/schedule" Write-Debug " current $($currentTimesMsg)" Write-Debug " new $($scheduleMsg)" if( $currentTimesMsg -ne $scheduleMsg ) { $clearedPrefix = $false $bothSchedules = & { $currentTimes | Write-Output $Schedule | Write-Output } | Where-Object { $_ } | Select-Object -Unique Write-Information "IIS ""$($AppPoolName)"" application pool periodic restart schedule " foreach ($time in $bothSchedules) { $flag = ' ' $action = '' if( $Schedule -notcontains $time ) { $flag = '-' $action = 'Remove' } elseif( $currentTimes -notcontains $time ) { $flag = '+' $action = 'Add' } if( $flag -eq ' ' ) { continue } $action = "$($action) Time" $target = "$($time) for '$($AppPoolName)' IIS application pool's periodic restart schedule" if( $PSCmdlet.ShouldProcess($target, $action) ) { Write-Information " $($flag) $($time)" $scheduleChanged = $true } if( -not $clearedPrefix ) { $clearedPrefix = $true } } if ($scheduleChanged) { $currentSchedule.Clear() foreach( $time in $Schedule ) { $add = $currentSchedule.CreateElement('add') try { $add.SetAttributeValue('value', $time) } catch { $msg = "Failed to add time ""$($time)"" to ""$($AppPoolName)"" IIS application pool's periodic " + "restart schedule: $($_)" Write-Error -Message $msg -ErrorAction Stop } if (-not $WhatIfPreference) { [void]$currentSchedule.Add($add) } } if (-not $WhatIfPreference) { Save-CIisConfiguration } } } } function Set-CIisAppPoolProcessModel { <# .SYNOPSIS Configures an IIS application pool's process model settings. .DESCRIPTION The `Set-CIisAppPoolProcessModel` function configures an IIS application pool's process model settings. Pass the name of the application pool to the `AppPoolName` parameter. Pass the process model configuration you want to one or more of the IdentityType, IdleTimeout, IdleTimeoutAction, LoadUserProfile, LogEventOnProcessModel, LogonType, ManualGroupMembership, MaxProcesses, Password, PingingEnabled, PingInterval, PingResponseTime, RequestQueueDelegatorIdentity, SetProfileEnvironment, ShutdownTimeLimit, StartupTimeLimit, and/or UserName parameters. See [Process Model Settings for an Application Pool <processModel>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/processmodel) for documentation on each setting. You can configure the IIS application pool defaults instead of a specific application pool by using the `AsDefaults` switch. If you want to ensure that any settings that may have gotten changed by hand are reset to their default values, use the `-Reset` switch. When set, the `-Reset` switch will reset each setting not passed as an argument to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/processmodel .EXAMPLE Set-CIisAppPoolProcessModel -AppPoolName 'ExampleTwo' -UserName 'user1' -Password $password Demonstrates how to set an IIS application pool to run as a custom identity. In this example, the application pool is updated to run as the user `user1`. All other process model settings are reset to their defaults. .EXAMPLE Set-CIisAppPoolProcessModel -AppPoolName 'ExampleOne' -UserName 'user1' -Password $password -Reset Demonstrates how to set *all* an IIS application pool's settings by using the `-Reset` switch. Any setting not passed as an argument is deleted, which resets it to its default value. In this example, the `ExampleOne` application pool's `userName` and `password` settings are updated and all other settings are deleted. .EXAMPLE Set-CIisAppPoolProcessModel -AsDefaults -IdleTimeout '00:00:00' Demonstrates how to configure the IIS application pool defaults process model settings by using the `AsDefaults` switch and not passing application pool name. In this example, the application pool defaults `idleTimeout` setting is set to `00:00:00`. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the application pool whose process model settings to set. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $AppPoolName, # If true, the function configures the IIS application pool defaults instead of a specific application pool. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS application pool's process model `identityType` setting. [ProcessModelIdentityType] $IdentityType, # Sets the IIS application pool's process model `idleTimeout` setting. [TimeSpan] $IdleTimeout, # Sets the IIS application pool's process model `idleTimeoutAction` setting. [IdleTimeoutAction] $IdleTimeoutAction, # Sets the IIS application pool's process model `loadUserProfile` setting. [bool] $LoadUserProfile, # Sets the IIS application pool's process model `logEventOnProcessModel` setting. [ProcessModelLogEventOnProcessModel] $LogEventOnProcessModel, # Sets the IIS application pool's process model `logonType` setting. [CIisProcessModelLogonType] $LogonType, # Sets the IIS application pool's process model `manualGroupMembership` setting. [bool] $ManualGroupMembership, # Sets the IIS application pool's process model `maxProcesses` setting. [UInt32] $MaxProcesses, # Sets the IIS application pool's process model `password` setting. [securestring] $Password, # Sets the IIS application pool's process model `pingingEnabled` setting. [bool] $PingingEnabled, # Sets the IIS application pool's process model `pingInterval` setting. [TimeSpan] $PingInterval, # Sets the IIS application pool's process model `pingResponseTime` setting. [TimeSpan] $PingResponseTime, # Sets the IIS application pool's process model `requestQueueDelegatorIdentity` setting. [String] $RequestQueueDelegatorIdentity, # Sets the IIS application pool's process model `setProfileEnvironment` setting. [bool] $SetProfileEnvironment, # Sets the IIS application pool's process model `shutdownTimeLimit` setting. [TimeSpan] $ShutdownTimeLimit, # Sets the IIS application pool's process model `startupTimeLimit` setting. [TimeSpan] $StartupTimeLimit, # Sets the IIS application pool's process model `userName` setting. [String] $UserName, # If set, the application pool process model setting for each parameter *not* passed is deleted, which resets it # to its default value. Otherwise, application pool process model settings whose parameters are not passed are # left in place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($AppPoolName) { $getArgs['Name'] = $AppPoolName } elseif ($AsDefaults) { $getArgs['Defaults'] = $true } $target = Get-CIisAppPool @getArgs if( -not $target ) { return } $targetMsg = 'IIS application pool defaults process model' if( $AppPoolName ) { $targetMsg = """$($AppPoolName)"" IIS application pool's process model" } Invoke-SetConfigurationAttribute -ConfigurationElement $target.ProcessModel ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset ` -Defaults (Get-CIIsAppPool -Defaults).ProcessModel ` -AsDefaults:$AsDefaults } function Set-CIisAppPoolRecycling { <# .SYNOPSIS Configures an IIS application pool's recycling settings. .DESCRIPTION The `Set-CIisAppPoolRecycling` function configures an IIS application pool's recycling settings. Pass the name of the application pool to the `AppPoolName` parameter. Pass the recycling configuration you want to one or more of the DisallowOverlappingRotation, DisallowRotationOnConfigChange, and/or LogEventOnRecycle parameters. See [Recycling Settings for an Application Pool <recycling>](https://learn.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/) for documentation on each setting. You can configure the IIS default application pool instead of a specific application pool by using the `AsDefaults` switch. If the `Reset` switch is set, each setting *not* passed as a parameter is deleted, which resets it to its default values. .LINK https://learn.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/ .EXAMPLE Set-CIisAppPoolRecycling -AppPoolName 'ExampleTwo' -DisallowOverlappingRotation $true -DisallowRotationOnConfigChange $true -LogEventOnRecycle None Demonstrates how to configure all an IIS application pool's recycling settings. .EXAMPLE Set-CIisAppPoolRecycling -AppPoolName 'ExampleOne' -DisallowOverlappingRotation $true -Reset Demonstrates how to set *all* an IIS application pool's recycling settings (even if not passing all parameters) by using the `-Reset` switch. In this example, the disallowOverlappingRotation setting is set to `$true`, and the `disallowRotationOnConfigChange` and `LogEventOnRecycle` settings are deleted, which resets them to their default values. .EXAMPLE Set-CIisAppPoolRecycling -AsDefaults -LogEventOnRecycle None Demonstrates how to configure the IIS application pool defaults recycling settings by using the `AsDefaults` switch and not passing the application pool name. In this example, the default application pool `logEventOnRecycle` recycle setting will be set to `None`. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the application pool whose recycling settings to configure. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $AppPoolName, # If true, the function configures the IIS default application pool instead of a specific application pool. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS application pool's recycling `disallowOverlappingRotation` setting. [bool] $DisallowOverlappingRotation, # Sets the IIS application pool's recycling `disallowRotationOnConfigChange` setting. [bool] $DisallowRotationOnConfigChange, # Sets the IIS application pool's recycling `logEventOnRecycle` setting. [RecyclingLogEventOnRecycle] $LogEventOnRecycle, # If set, each application pool recycling setting *not* passed as a parameter is deleted, which resets it to its # default value. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($AppPoolName) { $getArgs['Name'] = $AppPoolName } elseif ($AsDefaults) { $getArgs['Defaults'] = $true } $target = Get-CIisAppPool @getArgs if( -not $target ) { return } $targetMsg = 'default IIS application pool recycling' if( $AppPoolName ) { $targetMsg = """$($AppPoolName)"" IIS application pool's recycling" } Invoke-SetConfigurationAttribute -ConfigurationElement $target.recycling ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset ` -Defaults (Get-CIIsAppPool -Defaults).Recycling ` -AsDefaults:$AsDefaults } function Set-CIisCollection { <# .SYNOPSIS Sets the exact contents of an IIS configuration section collection. .DESCRIPTION The `Set-CIisCollection` function sets IIS configuration collection to a specific set of items. Pipe the collection items to the function (or pass them to the `InputObject` parameter). To set just the item's default value/attribute, pass a raw value. To set more than one of the item's attributes, pass them as a hashtable. By default, extra attributets on the item are ignored and left as-is. To remove any item attributes that aren't passed to the function, use the `Strict` switch. To make changes to a global configuration section, pass its path to the `SectionPath` parameter. To make changes to a site, directory, application, or virtual directory, pass its pass location path to the `LocationPath`. To make changes to a specific `[Microsoft.Web.Administration.ConfigurationElement]` object, pass it to the `ConfigurationElement` parameter. When making changes directly to ConfigurationElement objects, test that those changes are saved correctly to the IIS application host configuration. Some configuration has to be saved at the same time as its parent configuration elements are created (i.e. sites, application pools, etc.). Use the `Suspend-CIisAutoCommit` and `Resume-CIisAutoCommit` functions to ensure configuration gets committed simultaneously. .EXAMPLE @{ name = 'HttpLoggingModule' ; image = '%windir%\System32\inetsrv\loghttp.dll' } | Set-CIisCollection -SectionPath 'system.webServer/globalModules' Demonstrates how to set a collection to a specific list where each item in the collection has more than a single default attribute. The above command would result in the applicationHost.config file to be updated to be: <system.webServer> <globalModules> <add name="HttpLoggingModule" image="%windir%\System32\inetsrv\loghttp.dll" /> </globalModules> </system.webServer> .EXAMPLE 'default.aspx', 'index.html' | Set-CIisCollection -LocationPath 'SITE_NAME' -SectionPath 'system.webServer/defaultDocument' -Name 'files' Demonstrates how to set an IIS collection to have only a specific list of values by piping them to the function. After the above command runs, this will be in the applicationHost.config: <location path="SITE_NAME"> <system.webServer> <defaultDocument> <files> <add value="default.aspx" /> <add value="index.html" /> </files> </defaultDocument> </system.webServer> </location> #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The configuration element on which to operate. If not a collection, pass the name of the collection under this # element to the `Name` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The path to the collection. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The site the collection belongs to. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The name of the collection to change. [String] $Name, # Pass a hashtable for each item that has more than one attribute value to set. Otherwise, pass the attribute # value for the default attribute. [Parameter(ValueFromPipeline)] [Object[]] $InputObject, # The attribute name for the attribute that uniquely identifies each item in a collection. This is usually # automatically detected. [String] $UniqueKeyAttributeName, # By default, extra attributes on collection items are ignored. If this switch is set, any attributes not passed # to `Set-CIisCollection` are removed from collection items. [switch] $Strict ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $stopProcessing = $false $getSetArgs = @{} if ($Name) { $getSetArgs['Name'] = $Name } $displayPath = '' if ($ConfigurationElement) { $getSetArgs['ConfigurationElement'] = $ConfigurationElement $displayPath = $ConfigurationElement.ElementTagName } else { $getSetArgs['SectionPath'] = $SectionPath if ($LocationPath) { $getSetArgs['LocationPath'] = $LocationPath } $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath -LocationPath $LocationPath -SubSectionPath $Name } $collection = Get-CIisCollection @getSetArgs if (-not $collection) { $stopProcessing = $true return } $items = [List[hashtable]]::New() $keyValues = @{} if (-not $UniqueKeyAttributeName) { $UniqueKeyAttributeName = Get-CIisCollectionKeyName -Collection $collection if (-not $UniqueKeyAttributeName) { $msg = "Failed to set IIS configuration collection ${displayPath} because it does not have a unique " + 'key attribute. Use the "UniqueKeyAttributeName" parameter to specify the attribute name.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference $stopProcessing = $true return } } $removeSetArgs = @{} if ($UniqueKeyAttributeName) { $removeSetArgs['UniqueKeyAttributeName'] = $UniqueKeyAttributeName } } process { if ($stopProcessing) { return } foreach ($item in $InputObject) { if ($item -isnot [IDictionary]) { $item = @{ $UniqueKeyAttributeName = $item } } $items.Add($item) $keyValues[$item[$UniqueKeyAttributeName]] = $true } } end { if ($stopProcessing) { return } $itemsToRemove = $collection | ForEach-Object { $_.GetAttributeValue($UniqueKeyAttributeName) } | Where-Object { -not $keyValues.ContainsKey($_) } $itemsRemoved = $itemsToRemove | Remove-CIisCollectionItem @getSetArgs @removeSetArgs -SkipCommit $itemsModified = $items | Set-CIisCollectionItem @getSetArgs @removeSetArgs -SkipCommit -Strict:$Strict if ($itemsRemoved -or $itemsModified) { Save-CIisConfiguration } } } function Set-CIisCollectionItem { <# .SYNOPSIS Adds a new item to an IIS configuration collection. .DESCRIPTION The `Set-CIisCollectionItem` function adds a new item or updates the configuration of an existing IIS configuration collection item. Pipe the item value to the function, or pass it to the `InputObject` parameter. If the collection items you're configuring have only one attribute/value, pass just the value. Otherwise, pass a hashtable of attribute names/values. By default, only attributes passed in are added/set in the collection item. To delete any attributes not passed, use the `Strict` switch. To configure a collection that is part of a global configuration section, pass the configuration section's path to the `SectionPath` parameter. If the configuration section itself isn't a collection, pass the name of the collection to the `CollectionName` parameter. To configure a configuration section for a specific site, directory, application, or virtual directory, pass its location path to the `LocationPath` parameter. To configure a specific `[Microsoft.Web.Administration.ConfigurationElement]` item (i.e. a site, application pool, etc.), pass that object to the `ConfigurationElement` parameter. If the configuration element itself isn't a collection, pass the name of the object' collection property to the `CollectionName` parameter. When making changes directly to ConfigurationElement objects, test that those changes are saved correctly to the IIS application host configuration. Some configuration has to be saved at the same time as its parent configuration elements are created (i.e. sites, application pools, etc.). Use the `Suspend-CIisAutoCommit` and `Resume-CIisAutoCommit` functions to ensure configuration gets committed simultaneously. .EXAMPLE Set-CIisCollectionItem -SectionPath 'system.webServer/defaultDocument' -CollectionName 'files' -Value 'welcome.html' Demonstrates how to add an item to a configuration collection under a configuration section. This example will add "welcome.html" to the list of default documents. .EXAMPLE Set-CIisCollectionItem -LocationPath 'example.com' -SectionPath 'system.webServer/defaultDocument' -CollectionName 'files' -Value 'welcome.html' Demonstrates how to add an item to a site, directory, application, or virtual directory by using the `LocationPath` parameter. In this example, the "example.com" website will be configured to include "welcome.html" as a default document. .EXAMPLE @{ 'name' = 'X-Example' ; value = 'example' } | Set-CIisCollectionItem -SectionPath 'system.webServer/httpProtocol/customHeaders' -CollectionName 'files' Demonstrates how to add items that have multiple attributes by piping a hashtable of attribute names/values to the function. In this example, `Set-CIIsCollectionItem` will add `X-Example` HTTP header with a value of `example` to global configuration. #> [CmdletBinding(DefaultParameterSetName='BySectionPath')] param( # The `[Microsoft.Web.Administration.ConfigurationElement]` object to get as a collection or the parent element # of the collection element to get. If this is the parent element, pass the name of the child element collection # to the `CollectionName` parameter. [Parameter(Mandatory, ParameterSetName='ByConfigurationElement')] [ConfigurationElement] $ConfigurationElement, # The path to the collection's configuration section. [Parameter(Mandatory, ParameterSetName='BySectionPath')] [String] $SectionPath, # The location path to the site, directory, application, or virtual directory to configure. By default, the # global configuration will be updated. [Parameter(ParameterSetName='BySectionPath')] [String] $LocationPath, # The value for the IIS collection's identifying key. [Parameter(Mandatory, ValueFromPipeline)] [Object] $InputObject, # The name of the IIS collection to modify. If not provided, will use the SectionPath as the collection. [Alias('Name')] [String] $CollectionName, # The attribute name for the attribute that uniquely identifies each item in a collection. This is usually # automatically detected. [String] $UniqueKeyAttributeName, # If set, remove any attributes that aren't passed in. [switch] $Strict, # ***INTERNAL***. Do not use. [switch] $SkipCommit ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState function Write-Message { [CmdletBinding()] param( [Parameter(Mandatory)] [String] $Message ) if (-not $firstLineWritten) { Write-Information $firstLine Set-Variable -Name 'firstLineWritten' -Value $true -Scope 1 } if (-not $keyValueWritten) { Write-Information " ${keyValue}" Set-Variable -Name 'keyValueWritten' -Value $true -Scope 1 } Write-Information $Message } $firstLine = 'IIS configuration collection ' $firstLineWritten = $false $keyValueWritten = $false $save = $false $collectionArgs = @{} $elementPath = '' if ($ConfigurationElement) { $firstLine = "${firstLine}$($ConfigurationElement.ElementTagName)" $collectionArgs['ConfigurationElement'] = $ConfigurationElement $elementPath = $ConfigurationElement.ElementTagName if (Get-Member -Name 'SectionPath' -InputObject $ConfigurationElement) { $elementPath = $ConfigurationElement.SectionPath } } else { $displayPath = Get-CIisDisplayPath -SectionPath $SectionPath ` -LocationPath $locationPath ` -SubSectionPath $CollectionName $firstLine = "${firstLine}${displayPath}" $elementPath = $SectionPath $collectionArgs['SectionPath'] = $SectionPath if ($LocationPath) { $collectionArgs['LocationPath'] = $LocationPath } } if ($CollectionName) { $elementPath = "${elementPath}/$($CollectionName)" $collectionArgs['Name'] = $CollectionName } $collection = Get-CIisCollection @collectionArgs if (-not $collection) { return } if (-not $UniqueKeyAttributeName) { $UniqueKeyAttributeName = Get-CIisCollectionKeyName -Collection $collection if (-not $UniqueKeyAttributeName) { $msg = "Failed to set IIS configuration collection ${displayPath} because it does not have a unique " + 'key attribute. Use the "UniqueKeyAttributeName" parameter to specify the attribute name.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } } } process { if (-not $collection) { return } if ($InputObject -isnot [IDictionary]) { $InputObject = @{ $UniqueKeyAttributeName = $InputObject } } if (-not $InputObject.Contains($UniqueKeyAttributeName)) { $msg = "Failed to add item to collection ""$($collection.Path)"" because the attributes of the item are " + "missing a value for the key attribute ""${UniqueKeyAttributeName}""." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $keyValue = $InputObject[$UniqueKeyAttributeName] $item = $collection | Where-Object { $_.GetAttributeValue($UniqueKeyAttributeName) -eq $keyValue } if (-not $item) { $keyValueWritten = $true Write-Message " + ${keyValue}" $addElementName = $collection.Schema.AddElementNames $item = $collection.CreateElement($addElementName) foreach ($attrName in $InputObject.Keys) { if ($attrName -ne $UniqueKeyAttributeName) { Write-Message " + ${attrName} $($InputObject[$attrName])" } $item.SetAttributeValue($attrName, $InputObject[$attrName]) } [void]$collection.Add($item) $save = $true } else { $attrNameFieldLength = $InputObject.Keys | Select-Object -ExpandProperty 'Length' | Measure-Object -Maximum | Select-Object -ExpandProperty 'Maximum' $attrNameFormat = "{0,-${attrNameFieldLength}}" foreach ($attrName in $InputObject.Keys) { $expectedValue = $InputObject[$attrName] $actualValue = $item.GetAttributeValue($attrName) if ($expectedValue -eq $actualValue) { continue } $flag = ' ' $changeMsg = "${actualValue} -> ${expectedValue}" $isAddingAttr = $null -eq $actualValue -or '' -eq $actualValue if ($isAddingAttr) { $flag = '+' $changeMsg = $expectedValue } $attrDisplayName = $attrNameFormat -f $attrName Write-Message " ${flag} ${attrDisplayName} ${changeMsg}" $item.SetAttributeValue($attrName, $expectedValue) $save = $true } } if ($Strict) { foreach ($attr in $item.Attributes) { if ($InputObject.Contains($attr.Name)) { continue } Write-Message " - $($attr.Name)" [void]$attr.Delete() $save = $true } } } end { if (-not $save) { return } if ($SkipCommit) { return $true } Save-CIisConfiguration } } function Set-CIisConfigurationAttribute { <# .SYNOPSIS Sets attribute values on an IIS configuration section. .DESCRIPTION The `Set-CIisConfigurationAttribute` function can set a single attribute value or *all* attribute values on an IIS configuration section. Pass the virtual/location path of the website, application, virtual directory, or directory to configure to the `LocationPath` parameter. Pass the path to the configuration section to update to the `SectionPath` parameter. To set a single attribute value, and leave all other attributes unchanged, pass the attribute name to the `Name` parameter and its value to the `Value` parameter. If the new value is different than the current value, the value is changed and saved in IIS's applicationHost.config file inside a `location` section. To set *all* attributes on a configuration section, pass the attribute names and values in a hashtable to the `Attribute` parameter. Attributes in the hashtable will be updated to match the value in the hashtable. All other attributes will be left unchanged. You can delete attributes from the configuration section that aren't in the attributes hashtable by using the `Reset` switch. Deleting attributes reset them to their default values. To configure a global configuration section, omit the `LocationPath` parameter, or pass a `Microsoft.Web.Administration.ConfigurationElement` object to the `ConfigurationElement` parameter. `Set-CIisConfigurationAttribute` writes messages to PowerShell's information stream for each attribute whose value is changing, showing the current value and the new value. If an attribute's value is sensitive, use the `Sensitive` switch, and the attribute's current and new value will be masked with eight `*` characters. .EXAMPLE Set-CIisConfigurationAttribute -LocationPath 'SiteOne' -SectionPath 'system.webServer/httpRedirect' -Name 'destination' -Value 'http://example.com' Demonstrates how to call `Set-CIisConfigurationAttribute` to set a single attribute value for a website, application, virtual directory, or directory. In this example, the `SiteOne` website's http redirect "destination" setting is set `http://example.com`. All other attributes on the website's `system.webServer/httpRedirect` are left unchanged. .EXAMPLE Set-CIisConfigurationAttribute -LocationPath 'SiteTwo' -SectionPath 'system.webServer/httpRedirect' -Attribute @{ 'destination' = 'http://example.com'; 'httpResponseStatus' = 302 } Demonstrates how to set multiple attributes on a configuration section by piping a hashtable of attribute names and values to `Set-CIisConfigurationAttribute`. In this example, the `destination` and `httpResponseStatus` attributes are set to `http://example.com` and `302`, respectively. All other attributes on `system.webServer/httpRedirect` are preserved. .EXAMPLE Set-CIisConfigurationAttribute -LocationPath 'SiteTwo' -SectionPath 'system.webServer/httpRedirect' -Attribute @{ 'destination' = 'http://example.com' } -Reset Demonstrates how to delete attributes that aren't passed to the `Attribute` parameter by using the `Reset` switch. In this example, the "SiteTwo" website's HTTP Redirect setting's destination attribute is set to `http://example.com`, and all its other attributes (if they exist) are deleted (e.g. `httpResponseStatus`, `childOnly`, etc.), which resets the deleted attributes to their default values. .EXAMPLE Set-CIisConfigurationAttribute -SectionPath 'system.webServer/httpRedirect' -Name 'destination' -Value 'http://example.com' Demonstrates how to set attribute values on a global configuration section by omitting the `LocationPath` parameter. In this example, the global HTTP redirect destination is set to `http://example.com`. .EXAMPLE Set-CIisConfigurationAttribute -ConfigurationElement (Get-CIisAppPool -Name 'DefaultAppPool').Cpu -Name 'limit' -Value 10000 Demonstrates how to set attribute values on a configuration element object by passing the object to the `ConfigurationElement` parameter. In this case the "limit" setting for the "DefaultAppPool" application pool will be set. #> [CmdletBinding(SupportsShouldProcess)] param( # The name of the website whose attribute values to configure. [Parameter(Mandatory, ParameterSetName='AllByConfigPath', Position=0)] [Parameter(Mandatory, ParameterSetName='SingleByConfigPath', Position=0)] [String] $LocationPath, # The configuration section path to configure, e.g. # `system.webServer/security/authentication/basicAuthentication`. The path should *not* start with a forward # slash. You can also pass [Parameter(Mandatory, ParameterSetName='AllByConfigPath')] [Parameter(Mandatory, ParameterSetName='SingleByConfigPath')] [Parameter(Mandatory, ParameterSetName='AllForSection')] [Parameter(Mandatory, ParameterSetName='SingleForSection')] [String] $SectionPath, [Parameter(Mandatory, ParameterSetName='AllByConfigElement')] [Parameter(Mandatory, ParameterSetName='SingleByConfigElement')] [Microsoft.Web.Administration.ConfigurationElement] $ConfigurationElement, # A hashtable whose keys are attribute names and the values are the attribute values. Any attribute *not* in # the hashtable is ignored, unless the `All` switch is present, in which case, any attribute *not* in the # hashtable is removed from the configuration section (i.e. reset to its default value). [Parameter(Mandatory, ParameterSetName='AllByConfigElement')] [Parameter(Mandatory, ParameterSetName='AllByConfigPath')] [Parameter(Mandatory, ParameterSetName='AllForSection')] [hashtable] $Attribute, # The target element the change is being made on. Used in messages written to the console. The default is to # use the type and tag name of the ConfigurationElement. [Parameter(ParameterSetName='AllByConfigElement')] [Parameter(ParameterSetName='AllByConfigPath')] [Parameter(ParameterSetName='AllForSection')] [String] $Target, # Properties to skip and not change. These are usually private settings that we shouldn't be mucking with or # settings that capture current state, etc. [Parameter(ParameterSetName='AllByConfigElement')] [Parameter(ParameterSetName='AllByConfigPath')] [Parameter(ParameterSetName='AllForSection')] [String[]] $Exclude = @(), # If set, each setting on the configuration element whose attribute isn't in the `Attribute` hashtable is # deleted, which resets it to its default value. Otherwise, configuration element attributes not in the # `Attributes` hashtable left in place and not modified. [Parameter(ParameterSetName='AllByConfigElement')] [Parameter(ParameterSetName='AllByConfigPath')] [Parameter(ParameterSetName='AllForSection')] [switch] $Reset, # The name of the attribute whose value to set. Setting a single attribute will not affect any other attributes # in the configuration section. If you want other attribute values reset to default values, pass a hashtable # of attribute names and values to the `Attribute` parameter. [Parameter(Mandatory, ParameterSetName='SingleByConfigElement')] [Parameter(Mandatory, ParameterSetName='SingleByConfigPath')] [Parameter(Mandatory, ParameterSetName='SingleForSection')] [String] $Name, # The attribute's value. Setting a single attribute will not affect any other attributes in the configuration # section. If you want other attribute values reset to default values, pass a hashtable of attribute names and # values to the `Attribute` parameter. [Parameter(Mandatory, ParameterSetName='SingleByConfigElement')] [Parameter(Mandatory, ParameterSetName='SingleByConfigPath')] [Parameter(Mandatory, ParameterSetName='SingleForSection')] [AllowNull()] [AllowEmptyString()] [Object] $Value, # If the attribute's value is sensitive. If set, the attribute's value will be masked when written to the # console. [bool] $Sensitive, [ConfigurationElement] $Defaults, [switch] $AsDefaults ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState function Get-TypeName { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [AllowNull()] [AllowEmptyString()] [Object] $InputObject ) process { if ($null -eq $InputObject) { return '' } return "[$($InputObject.GetType().FullName -replace 'System\.', '')]" } } function Get-DisplayValue { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [AllowNull()] [AllowEmptyString()] [Object] $InputObject ) process { if ($null -eq $InputObject) { return " (null)" } if ($InputObject -is [String] -and [String]::IsNullOrEmpty($InputObject)) { return " (empty)" } if ($Sensitive -or $InputObject -is [securestring]) { return "********" } if ($InputObject -is [Enum]) { $valueAsEnum = [Enum]::Parse($InputObject.GetType().Name, $InputObject, $true) return "$($InputObject.ToString()) ($($valueAsEnum.ToString('D')))" } if ($Value -is [switch]) { return "$($Value.IsPresent)" } return "$($InputObject.ToString())" } } function Set-AttributeValue { [CmdletBinding()] param( [Parameter(Mandatory)] [Microsoft.Web.Administration.ConfigurationElement] $Element, [Parameter(Mandatory)] [Alias('Key')] [String] $Name, [AllowNull()] [AllowEmptyString()] [Object] $Value ) if( $Exclude -and $Name -in $Exclude ) { return } $Name = "$($Name.Substring(0, 1).ToLowerInvariant())$($Name.Substring(1, $Name.Length - 1))" $currentAttr = $Element.Attributes[$Name] if (-not $currentAttr) { $desc = Get-CIisDescription -ConfigurationElement $Element $msg = "Unable to set attribute ""$($Name)"" on ${desc} because that attribute doesn't exist. Valid " + "attributes are: $(($Element.Attributes | Select-Object -ExpandProperty 'Name') -join ', ')." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $currentValue = $currentAttr.Value $noCurrentValue = $null -eq $currentValue $hasCurrentValue = -not $noCurrentValue $currentValueMsg = $currentValue | Get-DisplayValue $defaultValue = $defaultValueSchema = $currentAttr.Schema.DefaultValue if (-not $AsDefaults -and $Defaults) { $defaultAttrSchema = $Defaults.GetAttribute($Name) if ($null -ne $defaultAttrSchema) { $defaultValue = $defaultAttrSchema.Value } } $defaultValueMsg = $defaultValue | Get-DisplayValue $defaultValueSchemaMsg = $defaultValueSchema | Get-DisplayValue $currentValueIsDefault = $currentValue -eq $defaultValue if ($currentValue -is [TimeSpan] -and $defaultValue -isnot [TimeSpan]) { $currentValueIsDefault = $currentValue.Ticks -eq $defaultValue } else { $currentValueIsNull = $null -eq $currentValue $defaultValueIsNull = $null -eq $defaultValue if (-not $currentValueIsNull -and -not $defaultValueIsNull) { $currentValueIsNumeric = [Microsoft.VisualBasic.Information]::IsNumeric($currentValue) $currentValueType = $currentValue.GetType() $defaultValueIsNumeric = [Microsoft.VisualBasic.Information]::IsNumeric($defaultValue) $defaultValueType = $defaultValue.GetType() if (-not $currentValueIsNumeric -and ` -not $defaultValueIsNumeric -and ` $currentValueType -ne $defaultValueType) { "Unable to safely determine the state of attribute ""$($Name)"" on IIS configuration element " + """$($Target)"": the current value's type " + "([$($currentValueType.FullName)] $($currentValueMsg)) is different than the default " + "value's type ([$($currentValueType.FullName)] $($currentValueMsg))." | Write-Warning } } } $valueMsg = $Value | Get-DisplayValue if ($Value -is [Enum] -and $null -ne $currentValue -and $currentValue -isnot [Enum]) { try { $currentValue = [Enum]::Parse($Value.GetType(), $currentValue.ToString(), $true) $currentValueMsg = $currentValue | Get-DisplayValue } catch { $Global:Error.RemoveAt(0) } } if ($currentValue -is [Enum] -and $null -ne $Value -and $Value -isnot [Enum]) { try { $Value = [Enum]::Parse($currentValue.GetType(), $Value.ToString(), $true) $valueMsg = $Value | Get-DisplayValue } catch { $Global:Error.RemoveAt(0) } } $newValue = $Value if ($newValue -is [securestring]) { $newValue = [pscredential]::New('i', $newValue).GetNetworkCredential().Password $currentValueMsg = '********' } if ($Sensitive -or $currentAttr.Name -eq 'password') { $valueMsg = '********' $currentValueMsg = '********' } $msgPrefix = " $($nameFormat -f $currentAttr.Name) " $emptyPrefixMsg = ' ' * $msgPrefix.Length Write-Debug "$($msgPrefix )current $($currentValue | Get-TypeName) $($currentValueMsg)" if (-not $AsDefaults) { "$($emptyPrefixMsg)schema default $($defaultValueSchema | Get-TypeName) $($defaultValueSchemaMsg)" | Write-Debug } Write-Debug "$($emptyPrefixMsg)default $($defaultValue | Get-TypeName) $($defaultValueMsg)" Write-Debug "$($emptyPrefixMsg)new $($newValue | Get-TypeName ) $($valueMsg)" Write-Debug "$($emptyPrefixMsg)current -eq new $($currentValue -eq $newValue)" Write-Debug "$($emptyPrefixMsg)current -eq default $($currentValue -eq $defaultValue)" if ($null -ne $currentValue) { Write-Debug "$($emptyPrefixMsg)current.Equals(new) $($currentValue.Equals($newValue))" Write-Debug "$($emptyPrefixMsg)current.Equals(default) $($currentValue.Equals($defaultValue))" } $whatIfTarget = "$($currentAttr.Name) for $($Target -replace '"', '''')" if (-not $PSBoundParameters.ContainsKey('Value') -and $hasCurrentValue) { if ($currentValueIsDefault) { return } $deletedMsg = "$($msgPrefix)- $($currentValueMsg)" $infoMessages.Add($deletedMsg) $action = "Remove Attribute" if ($PSCmdlet.ShouldProcess($whatIfTarget, $action)) { try { $currentAttr.Delete() } catch { $msg = "Exception resetting ""$($currentAttr.Name)"" on $($Target) to its default value (by " + "deleting it): $($_)" Write-Error -Message $msg return } [void]$updatedNames.Add($currentAttr.Name) } return } if ($currentValue -eq $newValue) { return } $changedMsg = "$($msgPrefix)$($currentValueMsg) -> $($valueMsg)" if ($noCurrentValue) { $changedMsg = "$($msgPrefix)+ $($valueMsg)" } [void]$infoMessages.Add($changedMsg) if ($PSCmdlet.ShouldProcess($whatIfTarget, 'Set Attribute')) { try { $ConfigurationElement.SetAttributeValue($currentAttr.Name, $newValue) } catch { $msg = "Exception setting ""$($currentAttr.Name)"" on $($Target): $($_)" Write-Error -Message $msg -ErrorAction Stop } [void]$updatedNames.Add($currentAttr.Name) } } if (-not $ConfigurationElement) { $locationPathArg = @{} if ($LocationPath) { $locationPathArg['LocationPath'] = $LocationPath } $ConfigurationElement = Get-CIisConfigurationSection -SectionPath $SectionPath @locationPathArg if( -not $ConfigurationElement ) { return } } $isConfigSection = $null -ne ($ConfigurationElement | Get-Member -Name 'SectionPath') if( -not $SectionPath -and $isConfigSection ) { $SectionPath = $ConfigurationElement.SectionPath } $attrNameFieldLength = $ConfigurationElement.Attributes | Select-Object -ExpandProperty 'Name' | Select-Object -ExpandProperty 'Length' | Measure-Object -Maximum | Select-Object -ExpandProperty 'Maximum' $nameFormat = "{0,-$($attrNameFieldLength)}" $updatedNames = [Collections.ArrayList]::New() $infoMessages = [Collections.Generic.List[String]]::New() if (-not $Target) { $Target = Get-CIisDescription -ConfigurationElement $ConfigurationElement } if ($Name) { Set-AttributeValue -Element $ConfigurationElement -Name $Name -Value $Value } else { $attrNames = $Attribute.Keys | Sort-Object foreach ($attrName in $attrNames) { Set-AttributeValue -Element $ConfigurationElement -Name $attrName -Value $Attribute[$attrName] } if ($Reset) { $attrNamesToDelete = $ConfigurationElement.Attributes | Where-Object 'Name' -NotIn $attrNames | Select-Object -ExpandProperty 'Name' | Sort-Object foreach ($attrName in ($attrNamesToDelete)) { Set-AttributeValue -Element $ConfigurationElement -Name $attrName } } } if ($updatedNames) { Write-Information $Target $infoMessages | ForEach-Object { Write-Information $_ } if (-not $WhatIfPreference) { Save-CIisConfiguration } } } function Set-CIisHttpHeader { <# .SYNOPSIS Sets an HTTP header for a website or a directory under a website. .DESCRIPTION If the HTTP header doesn't exist, it is created. If a header exists, its value is replaced. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .LINK Get-CIisHttpHeader .EXAMPLE Set-CIisHttpHeader -LocationPath 'SopwithCamel' -Name 'X-Flown-By' -Value 'Snoopy' Sets or creates the `SopwithCamel` website's `X-Flown-By` HTTP header to the value `Snoopy`. .EXAMPLE Set-CIisHttpHeader -LocationPath 'SopwithCamel/Engine' -Name 'X-Powered-By' -Value 'Root Beer' Sets or creates the `SopwithCamel` website's `Engine` sub-directory's `X-Powered-By` HTTP header to the value `Root Beer`. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The name of the website where the HTTP header should be set/created. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # The name of the HTTP header. [Parameter(Mandatory)] [String] $Name, # The value of the HTTP header. [Parameter(Mandatory)] [String] $Value ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sectionPath = 'system.webServer/httpProtocol' $httpProtocol = Get-CIisConfigurationSection -LocationPath $locationPath -VirtualPath $VirtualPath -SectionPath $sectionPath $headers = $httpProtocol.GetCollection('customHeaders') $header = $headers | Where-Object { $_['name'] -eq $Name } if( $header ) { $action = 'Set' $header['name'] = $Name $header['value'] = $Value } else { $action = 'Add' $addElement = $headers.CreateElement( 'add' ) $addElement['name'] = $Name $addElement['value'] = $Value [void] $headers.Add( $addElement ) } if ($VirtualPath) { $LocationPath = Join-CIisPath -Path $LocationPath, $VirtualPath } Save-CIisConfiguration -Target "IIS Website '$($LocationPath)'" -Action "$($action) $($Name) HTTP Header" } function Set-CIisHttpRedirect { <# .SYNOPSIS Turns on HTTP redirect for all or part of a website. .DESCRIPTION Configures all or part of a website to redirect all requests to another website/URL. Pass the virtual/location path to the website, application, virtual directory, or directory to configure to the `LocationPath` parameter. Pass the redirect destination to the `Destination` parameter. Pass the redirect HTTP response status code to the `HttpResponseStatus`. Pass `$true` or `$false` to the `ExactDestination` parameter. Pass `$true` or `$false` to the `ChildOnly` parameter. For each parameter that isn't provided, the current value of that attribute is not changed. To delete any attributes whose parameter isn't passed, use the `Reset` switch. Deleting an attribute resets it to its default value. .LINK http://www.iis.net/configreference/system.webserver/httpredirect#005 .LINK http://technet.microsoft.com/en-us/library/cc732969(v=WS.10).aspx .EXAMPLE Set-CIisHttpRedirect -LocationPath Peanuts -Destination 'http://new.peanuts.com' Redirects all requests to the `Peanuts` website to `http://new.peanuts.com`. .EXAMPLE Set-CIisHttpRedirect -LocationPath 'Peanuts/Snoopy/DogHouse' -Destination 'http://new.peanuts.com' Redirects all requests to the `/Snoopy/DogHouse` path on the `Peanuts` website to `http://new.peanuts.com`. .EXAMPLE Set-CIisHttpRedirect -LocationPath Peanuts -Destination 'http://new.peanuts.com' -StatusCode 'Temporary' Redirects all requests to the `Peanuts` website to `http://new.peanuts.com` with a temporary HTTP status code. You can also specify `Found` (HTTP 302), `Permanent` (HTTP 301), or `PermRedirect` (HTTP 308). .EXAMPLE Set-CIisHttpRedirect -LocationPath 'Peanuts' -Destination 'http://new.peanuts.com' -StatusCode 'Temporary' -Reset Demonstrates how to reset the attributes for any parameter that isn't passed to its default value by using the `Reset` switch. In this example, the `exactDestination` and `childOnly` HTTP redirect attributes are deleted and reset to their default value because they aren't being passed as arguments. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The site where the redirection should be setup. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # If true, enables HTTP redirect. Otherwise, disables it. [bool] $Enabled, # The destination to redirect to. [Parameter(Mandatory)] [String] $Destination, # The HTTP status code to use. Default is `Found` (`302`). Should be one of `Permanent` (`301`), # `Found` (`302`), `Temporary` (`307`), or `PermRedirect` (`308`). This is stored in IIS as a number. [Alias('StatusCode')] [CIisHttpRedirectResponseStatus] $HttpResponseStatus, # Redirect all requests to exact destination (instead of relative to destination). [bool] $ExactDestination, # Only redirect requests to content in site and/or path, but nothing below it. [bool] $ChildOnly, # If set, the HTTP redirect setting for each parameter *not* passed is deleted, which resets it to its default # value. Otherwise, HTTP redirect settings whose parameters are not passed are left in place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } $attrs = $PSBoundParameters | Copy-Hashtable -Key @('enabled', 'destination', 'httpResponseStatus', 'exactDestination', 'childOnly') Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath 'system.webServer/httpRedirect' ` -Attribute $attrs ` -Reset:$Reset } function Set-CIisMimeMap { <# .SYNOPSIS Creates or sets a file extension to MIME type map for an entire web server. .DESCRIPTION IIS won't serve static files unless they have an entry in the MIME map. Use this function to create/update a MIME map entry. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .LINK Get-CIisMimeMap .LINK Remove-CIisMimeMap .EXAMPLE Set-CIisMimeMap -FileExtension '.m4v' -MimeType 'video/x-m4v' Adds a MIME map to all websites so that IIS will serve `.m4v` files as `video/x-m4v`. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ForWebServer')] param( # The name of the website whose MIME type to set. [Parameter(Mandatory, ParameterSetName='ForWebsite', Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Parameter(ParameterSetName='ForWebsite')] [String] $VirtualPath = '', # The file extension to set. [Parameter(Mandatory)] [String] $FileExtension, # The MIME type to serve the files as. [Parameter(Mandatory)] [String] $MimeType ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getIisConfigSectionParams = @{ } if( $PSCmdlet.ParameterSetName -eq 'ForWebsite' ) { $getIisConfigSectionParams['LocationPath'] = $LocationPath $getIisConfigSectionParams['VirtualPath'] = $VirtualPath } $staticContent = Get-CIisConfigurationSection -SectionPath 'system.webServer/staticContent' @getIisConfigSectionParams $mimeMapCollection = $staticContent.GetCollection() $mimeMap = $mimeMapCollection | Where-Object { $_['fileExtension'] -eq $FileExtension } if( $mimeMap ) { $action = 'Set' $mimeMap['fileExtension'] = $FileExtension $mimeMap['mimeType'] = $MimeType } else { $action = 'Add' $mimeMap = $mimeMapCollection.CreateElement("mimeMap"); $mimeMap["fileExtension"] = $FileExtension $mimeMap["mimeType"] = $MimeType [void] $mimeMapCollection.Add($mimeMap) } Save-CIisConfiguration -Target "IIS MIME Map for $($FileExtension) Files" -Action "$($action) MIME Type" } function Set-CIisWebsite { <# .SYNOPSIS Configures an IIS website's settings. .DESCRIPTION The `Set-CIisWebsite` function configures an IIS website. Pass the name of the website to the `Name` parameter. Pass the website's ID to the `ID` parameter. If you want the server to not auto start, set `ServerAutoStart` to false: `-ServerAutoStart:$false` See [Site <site>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/) for documentation on each setting. You can configure the IIS default website instead of a specific website by using the `AsDefaults` switch. Only the `serverAutoStart` setting can be set on IIS's default website settings. If any `ServerAutoStart` is not passed, it is not changed. If you use the `-Reset` switch and omit a `ServerAutoStart` argument, the `serverAutoStart` setting will be deleted, which will reset it to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/ .EXAMPLE Set-CIisWebsite -SiteName 'ExampleTwo' -ID 53 -ServerAutoStart $false Demonstrates how to configure an IIS website's settings. .EXAMPLE Set-CIisWebsite -SiteName 'ExampleOne' -ID 53 -Reset Demonstrates how to set *all* an IIS website's settings by using the `-Reset` switch. In this example, the `id` setting is set to a custom value, and the `serverAutoStart` (the only other website setting) is deleted, which resets it to its default value. .EXAMPLE Set-CIisWebsite -AsDefaults -ServerAutoStart:$false Demonstrates how to configure the IIS default website's settings by using the `AsDefaults` switch and not passing the website name. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the website whose settings to configure. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $Name, # If true, the function configures the IIS default website instead of a specific website. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS website's `id` setting. Can not be used when setting site defaults. [Parameter(ParameterSetName='SetInstance')] [UInt32] $ID, # Sets the IIS website's `serverAutoStart` setting. [bool] $ServerAutoStart, # If set, the website setting for each parameter *not* passed is deleted, which resets it to its default value. # Otherwise, website settings whose parameters are not passed are left in place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $target = Get-CIisWebsite -Name $Name -Defaults:$AsDefaults if( -not $target ) { return } $attribute = @{} # Can't ever remove a site's ID, only change it (i.e. it must always be set to something). If user doesn't pass it, # set it to the website's current ID. if( -not $PSBoundParameters.ContainsKey('ID') -and ($target | Get-Member -Name 'Id') ) { $attribute['ID'] = $target.Id } $targetMsg = 'IIS website defaults' if( $Name ) { $targetMsg = "IIS website ""$($Name)""" } Invoke-SetConfigurationAttribute -ConfigurationElement $target ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Exclude @('state') ` -Attribute $attribute ` -Reset:$Reset ` -Defaults (Get-CIIsWebsite -Defaults) ` -AsDefaults:$AsDefaults } function Set-CIisWebsiteBinding { <# .SYNOPSIS Sets configuration on a website's bindings. .DESCRIPTION The `Set-CIisWebsiteBinding` function configures bindings on a website. Pass the website name to the `SiteName` parameter. Pass the SSL flags to set on all the website's HTTPS bindings to the `SslFlag` parameter. The function will update each HTTPS binding's SSL flags to match what is passed in. If they already match, the function does nothing. When setting the SslFlags setting, the function automatically skips non-HTTPS bindings. To only update a specific binding, pass the binding information to the `BindingInformation` parameter. You can also pipe binding information and/or actual binding objects. .EXAMPLE Set-CIisWebsiteBinding -SiteName 'example.com' -SslFlag ([Microsoft.Web.Administration.SslFlags]:Sni) Demonstrates how to require SNI (i.e. server name indication) on all of the example.com website's HTTPS bindings. .EXAMPLE Set-CIisWebsiteBinding -SiteName 'example.com' -BindingInformation '*:443:example.com' -SslFlag ([Microsoft.Web.Administration.SslFlags]:Sni) Demonstrates how to only update a specific binding by passing its binding information string to the `BindingInformation` parameter. .EXAMPLE Get-CIisWebsite -Name 'example.com' | Select-Object -ExpandProperty 'Bindings' | Where-Object 'Protocol' -EQ 'https' | Where-Object 'Host' -NE '' | Set-CIisWebsiteBinding -SiteName 'example.com' -SslFlag Sni Demonstrates that you can pipe binding objects into `Set-CIisWebsiteBinding`. In this example, only HTTPS and hostname bindings will get updated to have the `Sni` SSL flag. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( # The name of the website whose bindings to update. [Parameter(Mandatory)] [String] $SiteName, # The specific binding to set. Binding information must be in the IP-ADDRESS:PORT:HOSTNAME format. Can also be # piped in as strings or binding objects. [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [String] $BindingInformation, # The SSL flags for each of the website's HTTPS bindings. If a value for this parameter is omitted, the function # does nothing (i.e. existing SSL flags are not changed). [SslFlags] $SslFlag ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $bindings = Get-CIisWebsite -Name $SiteName | Select-Object -ExpandProperty 'Bindings' $updated = $false } process { foreach ($binding in $bindings) { if ($BindingInformation -and $binding.BindingInformation -ne $BindingInformation) { continue } if ($binding.Protocol -ne 'https') { continue } if (-not $PSBoundParameters.ContainsKey('SslFlag')) { continue } if ($binding.SslFlags -eq $SslFlag) { continue } if ($SslFlag.HasFlag([SslFlags]::Sni)) { if (-not $binding.Host) { $msg = "Unable to set SSL flags for binding ""$($binding.BindingInformation)"" because the " + """Sni"" flag is set but the binding doesn't have a hostname." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } } $msg = "${SiteName} $($binding.Protocol)/$($binding.BindingInformation) SslFlags $($binding.SslFlags) -> " + "${SslFlag}" Write-Information $msg $binding.SslFlags = $SslFlag $updated = $true } } end { if ($updated) { Save-CIisConfiguration } } } function Set-CIisWebsiteHttpsCertificate { <# .SYNOPSIS Sets a website's HTTPS certificate. .DESCRIPTION The `Set-CIisWebsiteHttpsCertificate` sets the HTTPS certificate for all of a website's HTTPS bindings. Pass the website name to the SiteName parameter, the certificate thumbprint to the `Thumbprint` parameter (the certificate should be in the LocalMachine's My store), and the website's application ID (a GUID that uniquely identifies the website) to the `ApplicationID` parameter. The function gets all the unique IP address/port HTTPS bindings and creates a binding for that address/port to the given certificate. Any HTTPS bindings on that address/port that don't use this thumbprint and application ID are removed. Make sure you call this method *after* you create a website's bindings. .EXAMPLE Set-CIisWebsiteHttpsCertificate -SiteName Peanuts -Thumbprint 'a909502dd82ae41433e6f83886b00d4277a32a7b' -ApplicationID $PeanutsAppID Binds the certificate whose thumbprint is `a909502dd82ae41433e6f83886b00d4277a32a7b` to the `Peanuts` website. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( # The name of the website whose HTTPS certificate is being set. [Parameter(Mandatory)] [string] $SiteName, # The thumbprint of the HTTPS certificate to use. [Parameter(Mandatory)] [string] $Thumbprint, # A GUID that uniquely identifies this website. Create your own. [Parameter(Mandatory)] [Guid] $ApplicationID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $SiteName if( -not $site ) { return } foreach ($binding in ($site.Bindings | Where-Object 'Protocol' -EQ 'https')) { $endpoint = $binding.Endpoint $portArg = @{ Port = $endpoint.Port; } if ($endpoint.Port -eq '*') { $portArg['Port'] = 443 } Set-CHttpsCertificateBinding -IPAddress $binding.Endpoint.Address ` @portArg ` -Thumbprint $Thumbprint ` -ApplicationID $ApplicationID } } function Set-CIisWebsiteID { <# .SYNOPSIS Sets a website's ID to an explicit number. .DESCRIPTION IIS handles assigning websites individual IDs. This method will assign a website explicit ID you manage (e.g. to support session sharing in a web server farm). If another site already exists with that ID, you'll get an error. When you change a website's ID, IIS will stop the site, but not start the site after saving the ID change. This function waits until the site's ID is changed, and then will start the website. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Set-CIisWebsiteID -SiteName Holodeck -ID 483 Sets the `Holodeck` website's ID to `483`. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] # The website name. [String] $SiteName, # The website's new ID. [Parameter(Mandatory)] [int] $ID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState "The $($PSCmdlet.MyInvocation.MyCommand.Name) function is obsolete. Use `Set-CIIsWebsite` instead." | Write-CIIsWarningOnce if( -not (Test-CIisWebsite -Name $SiteName) ) { Write-Error ('Website {0} not found.' -f $SiteName) return } $websiteWithID = Get-CIisWebsite | Where-Object { $_.ID -eq $ID -and $_.Name -ne $SiteName } if( $websiteWithID ) { Write-Error -Message ('ID {0} already in use for website {1}.' -f $ID,$SiteName) -Category ResourceExists return } $website = Get-CIisWebsite -SiteName $SiteName $startWhenDone = $false if( $website.ID -ne $ID ) { if( $PSCmdlet.ShouldProcess( ('website {0}' -f $SiteName), ('set site ID to {0}' -f $ID) ) ) { $startWhenDone = ($website.State -eq 'Started') $website.ID = $ID $website.CommitChanges() } } if( $PSBoundParameters.ContainsKey('WhatIf') ) { return } # Make sure the website's ID gets updated $website = $null $maxTries = 100 $numTries = 0 do { Start-Sleep -Milliseconds 100 $website = Get-CIisWebsite -SiteName $SiteName if( $website -and $website.ID -eq $ID ) { break } $numTries++ } while( $numTries -lt $maxTries ) if( -not $website -or $website.ID -ne $ID ) { Write-Error ('IIS:/{0}: site ID hasn''t changed to {1} after waiting 10 seconds. Please check IIS configuration.' -f $SiteName,$ID) } if( -not $startWhenDone ) { return } # Now, start the website. $numTries = 0 do { # Sometimes, the website is invalid and Start() throws an exception. try { if( $website ) { $null = $website.Start() } } catch { $website = $null } Start-Sleep -Milliseconds 100 $website = Get-CIisWebsite -SiteName $SiteName if( $website -and $website.State -eq 'Started' ) { break } $numTries++ } while( $numTries -lt $maxTries ) if( -not $website -or $website.State -ne 'Started' ) { Write-Error ('IIS:/{0}: failed to start website after setting ID to {1}' -f $SiteName,$ID) } } function Set-CIisWebsiteLimit { <# .SYNOPSIS Configures an IIS website's limits settings. .DESCRIPTION The `Set-CIisWebsiteLimit` function configures an IIS website's limits settings. Pass the name of the website to the `SiteName` parameter. Pass the limits configuration to one or more of the ConnectionTimeout, MaxBandwidth, MaxConnections, and/or MaxUrlSegments parameters. See [Limits for a Web Site <limits>](https://learn.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/limits) for documentation on each setting. You can configure the IIS default website instead of a specific website by using the `AsDefaults` switch. If the `Reset` switch is set, each setting *not* passed as a parameter is deleted, which resets it to its default value. .LINK https://learn.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/limits .EXAMPLE Set-CIisWebsiteLimit -SiteName 'ExampleTwo' -ConnectionTimeout '00:01:00' -MaxBandwidth 2147483647 -MaxConnections 2147483647 -MaxUrlSegments 16 Demonstrates how to configure all an IIS website's limits settings. .EXAMPLE Set-CIisWebsiteLimit -SiteName 'ExampleOne' -ConnectionTimeout 1073741823 -Reset Demonstrates how to set *all* an IIS website's limits settings (even if not passing all parameters) by using the `-Reset` switch. In this example, the `connectionTimeout` setting is set to `1073741823` and all other settings (`maxBandwidth`, `maxConnections`, and `maxUrlSegments`) are deleted, which resets them to their default values. .EXAMPLE Set-CIisWebsiteLimit -AsDefaults -ConnectionTimeout 536870911 Demonstrates how to configure the IIS website defaults limits settings by using the `AsDefaults` switch and not passing the website name. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the website whose limits settings to configure. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $SiteName, # If true, the function configures the IIS default website instead of a specific website. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS website's limits `connectionTimeout` setting. [TimeSpan] $ConnectionTimeout, # Sets the IIS website's limits `maxBandwidth` setting. [UInt32] $MaxBandwidth, # Sets the IIS website's limits `maxConnections` setting. [UInt32] $MaxConnections, # Sets the IIS website's limits `maxUrlSegments` setting. [UInt32] $MaxUrlSegments, # If set, each website limits setting *not* passed as a parameter is deleted, which resets it to its default # value. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $target = Get-CIisWebsite -Name $SiteName -Defaults:$AsDefaults if( -not $target ) { return } $targetMsg = 'default IIS website limits' if( $SiteName ) { $targetMsg = """$($SiteName)"" IIS website's limits" } Invoke-SetConfigurationAttribute -ConfigurationElement $target.Limits ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset ` -Defaults (Get-CIIsWebsite -Defaults).Limits ` -AsDefaults:$AsDefaults } function Set-CIisWebsiteLogFile { <# .SYNOPSIS Configures an IIS website's log file settings. .DESCRIPTION The `Set-CIisWebsiteLogFile` function configures an IIS website's log files settings. Pass the name of the website to the `SiteName` parameter. Pass the log files configuration you want to the `CustomLogPluginClsid`, `Directory`, `Enabled`, `FlushByEntryCountW3CLog`, `LocalTimeRollover`, `LogExtFileFlags`, `LogFormat`, `LogSiteID`, `LogTargetW3C`, `MaxLogLineLength`, `Period`, and `TruncateSize` parameters (see [Log Files for a Web Site <logFile>](https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/logfile/)) for documentation on what these settings are for. If you want to ensure that any settings that may have gotten changed by hand are reset to their default values, use the `-Reset` switch. When set, the `-Reset` switch will reset each setting not passed as an argument to its default value. .LINK https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/logfile/ .EXAMPLE Set-CIisWebsiteLogFile -AppPoolName 'Snafu' -Directory 'C:\logs' -MaxLogLineLength 32768 Demonstrates how to configure an IIS website's log file settings. In this example, `directory` will be set to `C:\logs` and `maxLogLineLength` will be set to `32768`. All other settings are unchanged. .EXAMPLE Set-CIisWebsiteLogFile -AppPoolName 'Snafu' -Directory 'C:\logs' -MaxLogLineLength 32768 -Reset Demonstrates how to set *all* an IIS website's log file settings by using the `-Reset` switch. In this example, the `directory` and `maxLogLineLength` settings are set to custom values, and all other settings are deleted, which resets them to their default values. .EXAMPLE Set-CIisWebsiteLogFile -AsDefaults -Directory 'C:\logs' -MaxLogLineLength 32768 Demonstrates how to configure the IIS website defaults log file settings by using the `AsDefaults` switch and not passing the website name. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(DefaultParameterSetName='SetInstance', SupportsShouldProcess)] param( # The name of the website whose log file settings to set. [Parameter(Mandatory, ParameterSetName='SetInstance', Position=0)] [String] $SiteName, # If true, the function configures IIS's application pool defaults instead of a specific application pool. [Parameter(Mandatory, ParameterSetName='SetDefaults')] [switch] $AsDefaults, # Sets the IIS website's log files `customLogPluginClsid` setting. [String] $CustomLogPluginClsid, # Sets the IIS website's log files `directory` setting. [String] $Directory, # Sets the IIS website's log files `enabled` setting. [bool] $Enabled, # Sets the IIS website's log files `flushByEntryCountW3CLog` setting. [UInt32] $FlushByEntryCountW3CLog, # Sets the IIS website's log files `localTimeRollover` setting. [bool] $LocalTimeRollover, # Sets the IIS website's log files `logExtFileFlags` setting. [LogExtFileFlags] $LogExtFileFlags, # Sets the IIS website's log files `logFormat` setting. [LogFormat] $LogFormat, # Sets the IIS website's log files `logSiteID` setting. [bool] $LogSiteID, # Sets the IIS website's log files `logTargetW3C` setting. [LogTargetW3C] $LogTargetW3C, # Sets the IIS website's log files `maxLogLineLength` setting. [UInt32] $MaxLogLineLength, # Sets the IIS website's log files `period` setting. [LoggingRolloverPeriod] $Period, # Sets the IIS website's log files `truncateSize` setting. [Int64] $TruncateSize, # If set, the website log file setting for each parameter *not* passed is deleted, which resets it to its # default value. By default, website log file settings whose parameters are not passed are left in place and not # modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $SiteName -Defaults:$AsDefaults if( -not $site ) { return } $targetMsg = "IIS website defaults log file" if( $SiteName ) { $targetMsg = """$($SiteName)"" IIS website's log file" } Invoke-SetConfigurationAttribute -ConfigurationElement $site.LogFile ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset ` -Defaults (Get-CIIsWebsite -Defaults).LogFile ` -AsDefaults:$AsDefaults } function Set-CIisWindowsAuthentication { <# .SYNOPSIS Configures the settings for Windows authentication. .DESCRIPTION By default, configures Windows authentication on a website. You can configure Windows authentication at a specific path under a website by passing the virtual path (*not* the physical path) to that directory. The changes only take effect if Windows authentication is enabled (see `Enable-CIisSecurityAuthentication`). Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .LINK http://blogs.msdn.com/b/webtopics/archive/2009/01/19/service-principal-name-spn-checklist-for-kerberos-authentication-with-iis-7-0.aspx .LINK Disable-CIisSecurityAuthentication .LINK Enable-CIisSecurityAuthentication .EXAMPLE Set-CIisWindowsAuthentication -LocationPath 'Peanuts/Snoopy/DogHouse' -UseKernelMode $false Configures Windows authentication on the `Snoopy/Doghouse` directory of the `Peanuts` site to not use kernel mode. .EXAMPLE Set-CIisWindowsAuthentication -LocationPath 'Peanuts' -Reset Configures Windows authentication on the `Peanuts` website to not use the default kernel mode because the `Reset` switch is given and the `UseKernelMode` parameter is not. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='New')] param( # The site where Windows authentication should be set. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath = '', # The value for the `authPersistNonNtlm` setting. [bool] $AuthPersistNonNtlm, # The value for the `authPersistSingleRequest` setting. [bool] $AuthPersistSingleRequest, # Enable Windows authentication. To disable Windows authentication you must explicitly set `Enabled` to # `$false`, e.g. `-Enabled $false`. [bool] $Enabled, # The value for the `useAppPoolCredentials` setting. [bool] $UseAppPoolCredentials, # The value for the `useKernelMode` setting. [Parameter(ParameterSetName='New')] [bool] $UseKernelMode, # OBSOLETE. Use the `UseKernelMode` parameter instead. [Parameter(ParameterSetName='Deprecated')] [switch] $DisableKernelMode, # If set, the anonymous authentication setting for each parameter *not* passed is deleted, which resets it to # its default value. Otherwise, anonymous authentication settings whose parameters are not passed are left in # place and not modified. [switch] $Reset ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $attrs = @{} $settingNames = @( 'authPersistNonNtlm', 'authPersistSingleRequest', 'enabled', 'useAppPoolCredentials', 'useKernelMode' ) $attrs = $PSBoundParameters | Copy-Hashtable -Key $settingNames if ($PSCmdlet.ParameterSetName -eq 'Deprecated') { "The $($PSCmdlet.MyInvocation.MyCommand.Name) function's ""DisableKernelMode"" switch is obsolete and will " + 'be removed in the next major version of Carbon.IIS. Use the new `UseKernelMode` parameter instead.' | Write-CIisWarningOnce $attrs['useKernelMode'] = -not $DisableKernelMode.IsPresent } if ($VirtualPath) { Write-CIisWarningOnce -ForObsoleteSiteNameAndVirtualPathParameter } $sectionPath = 'system.webServer/security/authentication/windowsAuthentication' Set-CIisConfigurationAttribute -LocationPath ($LocationPath, $VirtualPath | Join-CIisPath) ` -SectionPath $sectionPath ` -Attribute $attrs ` -Reset:$Reset } function Split-CIisLocationPath { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [String[]] $VirtualPath ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $VirtualPath) { return } return ($VirtualPath | ConvertTo-CIisVirtualPath -NoLeadingSlash).Split('/', 2) } } function Start-CIisAppPool { <# .SYNOPSIS Starts IIS application pools. .DESCRIPTION The `Start-CIisAppPool` starts IIS application pools. Pass the names of the application pools to the `Name` parameter, or pipe application pool objects or application pool names to `Start-CIisAppPool`. The function then starts the application pool and waits 30 seconds for the application pool to report that it has started. You can change the amount of time it waits with the `Timeout` parameter. If the application pool doesn't start before the timeout expires, the function writes an error. The Windows Process Activation Service (WAS) must be running in order for an application pool to start. The `Start-CIisAppPool` function will attempt to start the WAS if it exists and isn't running. .EXAMPLE Start-CIisAppPool -Name 'Default App Pool' Demonstrates how to start an application pool by passing its name to the `Name` parameter. .EXAMPLE Start-CIisAppPool -Name 'Default App Pool', 'Non-default App Pool' Demonstrates how to start multiple application pools by passing their names to the `Name` parameter. .EXAMPLE Get-CIisAppPool | Start-CIisAppPool Demonstrates how to start an application pool by piping it to `Start-CIisAppPool`. .EXAMPLE 'Default App Pool', 'Non-default App Pool' | Start-CIisAppPool Demonstrates how to start one or more application pools by piping their names to `Start-CIisAppPool`. .EXAMPLE Start-CIisAppPool -Name 'Default App Pool' -Timeout '00:00:10' Demonstrates how to change the amount of time `Start-CIisAppPool` waits for the application pool to start. In this example, it will wait 10 seconds. #> param( # One or more names of the application pools to start. You can also pipe one or more names to the function or # pipe one or more application pool objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Start-CIisAppPool` waits for an application pool to start before giving up and writing # an error. The default is 30 seconds. This doesn't mean the application pool actually has running worker # processes, just that it is reporting that is is started and available. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30) ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPools = $Name | ForEach-Object { Get-CIisAppPool -Name $_ } if (-not $appPools) { return } $waSvc = Get-Service -Name 'WAS' -ErrorAction Ignore if ($waSvc -and $waSvc.Status -ne [ServiceControllerStatus]::Running) { Write-Information "Starting ""$($waSvc.DisplayName)"" ($($waSvc.Name))." Start-Service -Name 'WAS' } $timer = [Diagnostics.Stopwatch]::New() foreach ($appPool in $appPools) { if ($appPool.State -eq [ObjectState]::Started) { continue } Write-Information "Starting IIS application pool ""$($appPool.Name)""." $state = $null $timer.Restart() $lastError = $null $numErrorsAtStart = $Global:Error.Count while ($null -eq $state -and $timer.Elapsed -lt $Timeout) { try { $state = $appPool.Start() } catch { # We're delaying saving configuration changes, so the app pool might not exist. Don't try to start # a non-existent app pool. if ($script:skipCommit) { return } $lastError = $_ Start-Sleep -Milliseconds 100 $appPool = Get-CIisAppPool -Name $appPool.Name } } if ($null -eq $state) { $msg = "Starting IIS application pool ""$($appPool.Name)"" threw an exception: $($lastError)." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } else { # Application pool started successfully, so remove the errors. $numErrorsToRemove = $Global:Error.Count - $numErrorsAtStart for ($idx = 0; $idx -lt $numErrorsToRemove; ++$idx) { $Global:Error.RemoveAt(0) } } if ($state -eq [ObjectState]::Started) { continue } while ($true) { if ($timer.Elapsed -gt $Timeout) { $msg = "IIS application pool ""$($appPool.Name)"" failed to start in less than $($Timeout)." Write-Error -Message $msg -ErrorAction $ErrorActionPreference break } $appPool = Get-CIisAppPool -Name $appPool.Name if ($appPool.State -eq [ObjectState]::Started) { break } Start-Sleep -Milliseconds 100 } } } } function Start-CIisWebsite { <# .SYNOPSIS Starts IIS websites. .DESCRIPTION The `Start-CIisWebsite` starts IIS websites. Pass the names of the websites to the `Name` parameter, or pipe website objects or website names to `Start-CIisWebsite`. The function then starts the website and waits 30 seconds for the website to report that it has started. You can change the amount of time it waits with the `Timeout` parameter. If the website doesn't start before the timeout expires, the function writes an error. The World Wide Web Publishing Service (W3SVC) must be running for a website to start. The `Start-CIisWebsite` will attempt to start the W3SVC if it exists and isn't running. .EXAMPLE Start-CIisWebsite -Name 'Default Website' Demonstrates how to start a website by passing its name to the `Name` parameter. .EXAMPLE Start-CIisWebsite -Name 'Default Website', 'Non-default Website' Demonstrates how to start multiple websites by passing their names to the `Name` parameter. .EXAMPLE Get-CIisWebsite | Start-CIisWebsite Demonstrates how to start a website by piping it to `Start-CIisWebsite`. .EXAMPLE 'Default Website', 'Non-default Website' | Start-CIisWebsite Demonstrates how to start one or more websites by piping their names to `Start-CIisWebsite`. .EXAMPLE Start-CIisWebsite -Name 'Default Website' -Timeout '00:00:10' Demonstrates how to change the amount of time `Start-CIisWebsite` waits for the website to start. In this example, it will wait 10 seconds. #> param( # One or more names of the websites to start. You can also pipe one or more names to the function or # pipe one or more website objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Start-CIisWebsite` waits for a website to start before giving up and writing # an error. The default is 30 seconds. This doesn't mean the website actually has running worker # processes, just that it is reporting that is is started and available. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30) ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $websites = $Name | ForEach-Object { Get-CIisWebsite -Name $_ } if (-not $websites) { return } $w3Svc = Get-Service 'W3SVC' -ErrorAction Ignore if ($w3Svc -and $w3Svc.Status -ne [ServiceControllerStatus]::Running) { Write-Information "Starting ""$($w3Svc.DisplayName)"" ($($w3Svc.Name))." Start-Service -Name 'W3SVC' } $timer = [Diagnostics.Stopwatch]::New() foreach ($website in $websites) { if ($website.State -eq [ObjectState]::Started) { continue } $siteAppPoolName = $website.Applications | Where-Object 'Path' -eq '/' | Select-Object -ExpandProperty 'ApplicationPoolName' if (-not (Test-CIisAppPool -Name $siteAppPoolName)) { $msg = "Unable to start website ""$($website.Name)"" because its application pool, " + """$($siteAppPoolName)"", does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } Write-Information "Starting IIS website ""$($website.Name)""." $state = $null $lastError = $null $timer.Restart() $numErrorsAtStart = $Global:Error.Count while ($null -eq $state -and $timer.Elapsed -lt $Timeout) { try { $state = $website.Start() } catch { if ($script:skipCommit) { return } $lastError = $_ Start-Sleep -Milliseconds 100 $website = Get-CIisWebsite -Name $website.Name } } if ($null -eq $state) { $msg = "Starting IIS website ""$($website.Name)"" threw an exception: $($lastError)." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } else { # Site started successfully, so remove the errors. $numErrorsToRemove = $Global:Error.Count - $numErrorsAtStart for ($idx = 0; $idx -lt $numErrorsToRemove; ++$idx) { $Global:Error.RemoveAt(0) } } if ($state -eq [ObjectState]::Started) { continue } while ($true) { $website = Get-CIisWebsite -Name $website.Name if ($website.State -eq [ObjectState]::Started) { break } if ($timer.Elapsed -gt $Timeout) { $msg = "IIS website ""$($website.Name)"" failed to start in less than $($Timeout)." Write-Error -Message $msg -ErrorAction $ErrorActionPreference break } Start-Sleep -Milliseconds 100 } } } } function Stop-CIisAppPool { <# .SYNOPSIS Stops an IIS application pool. .DESCRIPTION The `Stop-CIisAppPool` stops an IIS application pool. Pass the names of the application pools to the `Name` parameter, or pipe application pool objects or application pool names to `Stop-CIisAppPool`. The function will stop the application pool, then waits 30 seconds for it to stop (you can control this wait period with the `Timeout` parameter). If the application pool hasn't stopped, the function writes an error, and returns. You can use the `Force` (switch) to indicate to `Stop-CIisAppPool` that it should attempt to kill/stop any of the application pool's worker processes if the application pool doesn't stop before the timeout completes. If killing the worker processes fails, the function writes an error. This function disposes the current server manager object that Carbon.IIS uses internally. Make sure you have no pending, unsaved changes when calling `Stop-CIisAppPool`. .EXAMPLE Stop-CIisAppPool -Name 'Default App Pool' Demonstrates how to stop an application pool by passing its name to the `Name` parameter. .EXAMPLE Stop-CIisAppPool -Name 'Default App Pool', 'Non-default App Pool' Demonstrates how to stop multiple application pools by passing their names to the `Name` parameter. .EXAMPLE Get-CIisAppPool | Stop-CIisAppPool Demonstrates how to stop an application pool by piping it to `Stop-CIisAppPool`. .EXAMPLE 'Default App Pool', 'Non-default App Pool' | Stop-CIisAppPool Demonstrates how to stop one or more application pools by piping their names to `Stop-CIisAppPool`. .EXAMPLE Stop-CIisAppPool -Name 'Default App Pool' -Timeout '00:00:10' Demonstrates how to change the amount of time `Stop-CIisAppPool` waits for the application pool to stop. In this example, it will wait 10 seconds. .EXAMPLE Stop-CIisAppPool -Name 'Default App Pool' -Force Demonstrates how to stop an application pool that won't stop by using the `Force` (switch). After waiting for the application pool to stop, if it is still running and the `Force` (switch) is used, `Stop-CIisAppPool` will try to kill the application pool's worker processes. #> [CmdletBinding()] param( # One or more names of the application pools to stop. You can also pipe one or more names to the function or # pipe one or more application pool objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Stop-CIisAppPool` waits for an application pool to stop before giving up and writing # an error. The default is 30 seconds. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30), # If set, and an application pool fails to stop on its own, `Stop-CIisAppPool` will attempt to kill the # application pool worker processes. [switch] $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPools = $Name | ForEach-Object { Get-CIisAppPool -Name $_ } if (-not $appPools) { return } $timer = [Diagnostics.Stopwatch]::New() foreach ($appPool in $appPools) { if ($appPool.State -eq [ObjectState]::Stopped) { continue } Write-Information "Stopping IIS application pool ""$($appPool.Name)""." $state = $null $lastError = $null $timer.Restart() $numErrors = $Global:Error.Count while ($null -eq $state -and $timer.Elapsed -lt $Timeout) { try { $state = $appPool.Stop() } catch { if ($script:skipCommit) { return } $lastError = $_ Start-Sleep -Milliseconds 100 $appPool = Get-CIisAppPool -Name $appPool.Name } } if ($null -eq $state) { $msg = "Exception stopping IIS application pool ""$($appPool.Name)"": $($lastError)" Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } # Clear any errors that occurred since the app pool eventually stopped. for ($idx = $numErrors; $idx -lt $Global:Error.Count; ++$idx) { $Global:Error.RemoveAt(0) } if ($state -eq [ObjectState]::Stopped) { continue } while ($true) { $appPool = Get-CIisAppPool -Name $appPool.Name if ($appPool.State -eq [ObjectState]::Stopped) { break } if ($timer.Elapsed -gt $Timeout) { if ($Force) { $appPool = Get-CIisAppPool -Name $appPool.Name foreach ($wp in $appPool.WorkerProcesses) { $msg = "IIS application pool ""$($appPool.Name)"" failed to stop in less than " + "$($Timeout): forcefully stopping worker process $($wp.ProcessId)." Write-Warning $msg Stop-Process -id $wp.ProcessId -Force -ErrorAction Ignore $timer.Restart() while ($true) { if (-not (Get-Process -Id $wp.ProcessId -ErrorAction Ignore)) { break } if ($timer.Elapsed -gt $Timeout) { $msg = "IIS application pool ""$($appPool.Name)"" failed to stop in less than " + "$($Timeout) and its worker process $($wp.ProcessId) also failed to stop " + "in less than $($Timeout)." Write-Error -Message $msg break } Start-Sleep -Milliseconds 100 } } break } $msg = "IIS application pool ""$($appPool.Name)"" failed to stop in ""$($Timeout)""." Write-Error -Message $msg -ErrorAction $ErrorActionPreference break } Start-Sleep -Milliseconds 100 } } } } function Stop-CIisWebsite { <# .SYNOPSIS Stops an IIS website. .DESCRIPTION The `Stop-CIisWebsite` stops an IIS website. Pass the names of the websites to the `Name` parameter, or pipe website objects or website names to `Stop-CIisWebsite`. The function will stop the website, then waits 30 seconds for it to stop (you can control this wait period with the `Timeout` parameter). If the website hasn't stopped, the function writes an error, and returns. .EXAMPLE Stop-CIisWebsite -Name 'Default Website' Demonstrates how to stop a website by passing its name to the `Name` parameter. .EXAMPLE Stop-CIisWebsite -Name 'Default Website', 'Non-default Website' Demonstrates how to stop multiple websites by passing their names to the `Name` parameter. .EXAMPLE Get-CIisWebsite | Stop-CIisWebsite Demonstrates how to stop a website by piping it to `Stop-CIisWebsite`. .EXAMPLE 'Default Website', 'Non-default Website' | Stop-CIisWebsite Demonstrates how to stop one or more websites by piping their names to `Stop-CIisWebsite`. .EXAMPLE Stop-CIisWebsite -Name 'Default Website' -Timeout '00:00:10' Demonstrates how to change the amount of time `Stop-CIisWebsite` waits for the website to stop. In this example, it will wait 10 seconds. #> [CmdletBinding()] param( # One or more names of the websites to stop. You can also pipe one or more names to the function or # pipe one or more website objects. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name, # The amount of time `Stop-CIisWebsite` waits for a website to stop before giving up and writing # an error. The default is 30 seconds. [TimeSpan] $Timeout = [TimeSpan]::New(0, 0, 30) ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $websites = $Name | ForEach-Object { Get-CIisWebsite -Name $_ } if (-not $websites) { return } $timer = [Diagnostics.Stopwatch]::New() foreach ($website in $websites) { if ($website.State -eq [ObjectState]::Stopped) { continue } Write-Information "Stopping IIS website ""$($website.Name)""." $state = $null $lastError = $null $timer.Restart() $numErrorsAtStart = $Global:Error.Count while ($null -eq $state -and $timer.Elapsed -lt $Timeout) { try { $state = $website.Stop() } catch { if ($script:skipCommit) { return } $lastError = $_ Start-Sleep -Milliseconds 100 $website = Get-CIisWebsite -Name $website.Name } } if ($null -eq $state) { $msg = "Failed to stop IIS website ""$($website.Name)"": $($lastError)" Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } else { # Site stopped successfully, so remove the errors. $numErrorsToRemove = $Global:Error.Count - $numErrorsAtStart for ($idx = 0; $idx -lt $numErrorsToRemove; ++$idx) { $Global:Error.RemoveAt(0) } } if ($state -eq [ObjectState]::Stopped) { continue } while ($true) { $website = Get-CIisWebsite -Name $website.Name if ($website.State -eq [ObjectState]::Stopped) { break } if ($timer.Elapsed -gt $Timeout) { $msg = "IIS website ""$($website.Name)"" failed to stop in ""$($Timeout)""." Write-Error -Message $msg -ErrorAction $ErrorActionPreference break } Start-Sleep -Milliseconds 100 } } } } function Suspend-CIisAutoCommit { <# .SYNOPSIS Stops Carbon.IIS functions from committing changes to IIS. .DESCRIPTION The `Suspend-CIisAutoCommit` functions stops Carbon.IIS functions from committing changes to IIS. Some IIS configuration is only committed correctly when an item is first saved/created. To ensure that all the changes made by Carbon.IIS are committed at the same time, call `Suspend-CIisAutoCommit`, make your changes, then call `Resume-CIisAutoCommit -Save` to start auto-committing again *and* to commit all uncomitted changes. .EXAMPLE Suspend-CIisAutoCommit Demonstrates how to call this function. #> [CmdletBinding()] param( ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $script:skipCommit = $true } function Test-CIisApplicationHostElement { [CmdletBinding()] param( [Parameter(Mandatory)] [String] $XPath, [String] $LocationPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $xml = [xml](Get-Content -Path $script:applicationHostPath -Raw) $element = $xml.DocumentElement if ($LocationPath) { $element = $element.SelectSingleNode("location[@path = ""$($LocationPath.TrimStart('/'))""]") if (-not $element) { return $false } } $element = $element.SelectSingleNode($XPath) if ($element) { return $true } return $false } function Test-CIisAppPool { <# .SYNOPSIS Checks if an app pool exists. .DESCRIPTION Returns `True` if an app pool with `Name` exists. `False` if it doesn't exist. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Test-CIisAppPool -Name Peanuts Returns `True` if the Peanuts app pool exists, `False` if it doesn't. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The name of the app pool. $Name ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPool = Get-CIisAppPool -Name $Name -ErrorAction Ignore if( $appPool ) { return $true } return $false } function Test-CIisConfigurationSection { <# .SYNOPSIS Tests a configuration section. .DESCRIPTION You can test if a configuration section exists or wheter it is locked. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .OUTPUTS System.Boolean. .EXAMPLE Test-CIisConfigurationSection -SectionPath 'system.webServer/I/Do/Not/Exist' Tests if a configuration section exists. Returns `False`, because the given configuration section doesn't exist. .EXAMPLE Test-CIisConfigurationSection -SectionPath 'system.webServer/cgi' -Locked Returns `True` if the global CGI section is locked. Otherwise `False`. .EXAMPLE Test-CIisConfigurationSection -SectionPath 'system.webServer/security/authentication/basicAuthentication' -SiteName `Peanuts` -VirtualPath 'SopwithCamel' -Locked Returns `True` if the `Peanuts` website's `SopwithCamel` sub-directory's `basicAuthentication` security authentication section is locked. Otherwise, returns `False`. #> [CmdletBinding(DefaultParameterSetName='CheckExists')] param( [Parameter(Mandatory)] # The path to the section to test. [String] $SectionPath, # The name of the site whose configuration section to test. Optional. The default is the global configuration. [Parameter(Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [Alias('Path')] [String] $VirtualPath, # Test if the configuration section is locked. [Parameter(Mandatory, ParameterSetName='CheckLocked')] [switch] $Locked ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{} if ($LocationPath) { $getArgs['LocationPath'] = $LocationPath $getArgs['VirtualPath'] = $VirtualPath } $section = Get-CIisConfigurationSection -SectionPath $SectionPath @getArgs -ErrorAction SilentlyContinue if( $PSCmdlet.ParameterSetName -eq 'CheckExists' ) { if( $section ) { return $true } else { return $false } } if( -not $section ) { if ($VirtualPath) { $LocationPath = Join-CIisPath -Path $LocationPath, $VirtualPath } Write-Error "IIS:$($LocationPath): section $($SectionPath) not found." -ErrorAction $ErrorActionPreference return } if( $PSCmdlet.ParameterSetName -eq 'CheckLocked' ) { return $section.OverrideMode -eq 'Deny' } } function Test-CIisSecurityAuthentication { <# .SYNOPSIS Tests if IIS authentication types are enabled or disabled on a site and/or virtual directory under that site. .DESCRIPTION You can check if anonymous, basic, or Windows authentication are enabled. There are switches for each authentication type. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .OUTPUTS System.Boolean. .EXAMPLE Test-CIisSecurityAuthentication -SiteName Peanuts -Anonymous Returns `true` if anonymous authentication is enabled for the `Peanuts` site. `False` if it isn't. .EXAMPLE Test-CIisSecurityAuthentication -SiteName Peanuts -VirtualPath Doghouse -Basic Returns `true` if basic authentication is enabled for`Doghouse` directory under the `Peanuts` site. `False` if it isn't. #> [CmdletBinding()] param( # The site where anonymous authentication should be set. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the LocationPath parameter instead. [Alias('Path')] [String] $VirtualPath, # Tests if anonymous authentication is enabled. [Parameter(Mandatory, ParameterSetName='Anonymous')] [switch] $Anonymous, # Tests if basic authentication is enabled. [Parameter(Mandatory, ParameterSetName='Basic')] [switch] $Basic, # Tests if digest authentication is enabled. [Parameter(Mandatory, ParameterSetName='Digest')] [switch] $Digest, # Tests if Windows authentication is enabled. [Parameter(Mandatory, ParameterSetName='Windows')] [switch] $Windows ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getConfigArgs = @{ $PSCmdlet.ParameterSetName = $true } $authSettings = Get-CIisSecurityAuthentication -LocationPath (Join-CIisPath -Path $LocationPath, $VirtualPath) ` @getConfigArgs return ($authSettings.GetAttributeValue('enabled') -eq 'true') } function Test-CIisWebsite { <# .SYNOPSIS Tests if a website exists. .DESCRIPTION Returns `True` if a website with name `Name` exists. `False` if it doesn't. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Test-CIisWebsite -Name 'Peanuts' Returns `True` if the `Peanuts` website exists. `False` if it doesn't. #> [CmdletBinding()] param( # The name of the website whose existence to check. Wildcards supported. [Parameter(Mandatory)] [String] $Name ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $site = Get-CIisWebsite -Name $Name -ErrorAction Ignore if( $site ) { return $true } return $false } function Uninstall-CIisApplication { <# .SYNOPSIS Delete an IIS application. .DESCRIPTION The `Uninstall-CIisApplication` function deletes an application. Pass the application's site name to the `SiteName` parameter. Pass the application's virtual path to the `VirtualPath` parameter. If the application exists, it is deleted. If it doesn't exist, nothing happens. The function will not delete a site's default, root application at virtual path `/` and will instead write an error. .EXAMPLE Uninstall-CIisApplication -SiteName 'site' -VirtualPath '/some/app' Demonstrates how to use this function to delete an IIS application. In this example, the `/some/app` application under the `site` site will be removed, if it exists. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.Application])] param( # The applicatoin's site. [Parameter(Mandatory)] [String] $SiteName, # The application's virtual path. [Parameter(Mandatory)] [Alias('Name')] [String] $VirtualPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sites = Get-CIisWebsite -Name $SiteName if( -not $sites ) { return } $save = $false foreach ($site in $sites) { $apps = Get-CIisApplication -SiteName $site.Name -VirtualPath $VirtualPath if (-not $apps) { Write-Verbose "IIS application ""${VirtualPath}"" under site ""$($site.Name)"" does not exist." continue } foreach ($app in $apps) { if ($app.Path -eq '/') { $msg = "Failed to delete IIS application ""$($app.Path)}"" under site ""$($site.Name)"" because it " + 'is the root, default application. Use the "Uninstall-CIisWebsite" function to uninstall IIS ' + 'sites.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } Write-Information "Deleting IIS application ""$($app.Path)"" under site ""$($site.Name)""." $apps = Get-CIisCollection -ConfigurationElement $site $appToRemove = $apps | Where-Object { $_.GetAttributeValue('path') -eq $app.Path } $apps.Remove($appToRemove) $save = $true } } if ($save) { Save-CIisConfiguration } } function Uninstall-CIisAppPool { <# .SYNOPSIS Removes an IIS application pool. .DESCRIPTION If the app pool doesn't exist, nothing happens. Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Uninstall-CIisAppPool -Name Batcave Removes/uninstalls the `Batcave` app pool. #> [CmdletBinding(SupportsShouldProcess)] param( # The name of the app pool to remove. [Parameter(Mandatory)] [String] $Name ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPool = Get-CIisAppPool -Name $Name -ErrorAction Ignore if( -not $appPool ) { return } $target = "IIS Application Pool $($Name)" if ($PSCmdlet.ShouldProcess($target, 'Stop')) { # Stop the app pool first, otherwise it can sometimes still be running after this function returns. Stop-CIisAppPool -Name $Name } $appPool = Get-CIisAppPool -Name $Name if ($PSCmdlet.ShouldProcess($target, 'Remove')) { Write-Information -Message "Removing IIS application pool ""$($Name)""." $appPool.Delete() } Save-CIisConfiguration } function Uninstall-CIisVirtualDirectory { <# .SYNOPSIS Delete an IIS virtual directory. .DESCRIPTION The `Uninstall-CIisVirtualDirectory` function deletes a virtual directory. Pass the virtual directory's site name to the `SiteName` parameter. Pass the virtual directory's virtual path to the `VirtualPath` parameter. If the virtual directory exists, it is deleted. If it doesn't exist, nothing happens. If the virtual directory is under an application, pass the application's path to the `ApplicationPath` parameter. The function will not delete a site's default, root application at virtual path `/` and will instead write an error. .EXAMPLE Uninstall-CIisVirtualDirectory -SiteName 'site' -VirtualPath '/some/vdir' Demonstrates how to use this function to delete an IIS virtual directory. In this example, the `/some/vdir` virtual directory under the `site` site will be removed, if it exists. .EXAMPLE Uninstall-CIisVirtualDirectory -SiteName 'site' -ApplicationPath 'app' -VirtualPath '/some/vdir' Demonstrates how to use this function to delete an IIS virtual directory that exists under an application. In this example, the `/some/vdir` virtual directory under the `site` site's '/app' app will be removed, if it exists. #> [CmdletBinding()] [OutputType([Microsoft.Web.Administration.Application])] param( # The virtual directory's site. [Parameter(Mandatory)] [String] $SiteName, # The virtual directory's virtual path. [Parameter(Mandatory)] [Alias('Name')] [String] $VirtualPath, # The path of the virtual directory's application. The default is to look for the the virtual directory under # the site. [String] $ApplicationPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sites = Get-CIisWebsite -Name $SiteName if( -not $sites ) { return } if (-not $ApplicationPath) { $ApplicationPath = '/' } $ApplicationPath = $ApplicationPath | ConvertTo-CIisVirtualPath $save = $false foreach ($site in $sites) { $app = $null $appDesc = '' $apps = Get-CIisApplication -SiteName $site.Name -VirtualPath $ApplicationPath if (-not $apps) { continue } foreach ($app in $apps) { $appDesc = '' $suggestedCmd = 'Uninstall-CIisWebsite' if ($app.Path -ne '/') { $appDesc = " under application ""$($app.Path)""" $suggestedCmd = 'Uninstall-CIisApplication' } $desc = "IIS virtual directory ""${VirtualPath}""${appDesc} under site ""$($site.Name)""" $vdirs = Get-CIisVirtualDirectory -SiteName $site.Name ` -VirtualPath $VirtualPath ` -ApplicationPath $ApplicationPath ` -ErrorAction Ignore if (-not $vdirs) { Write-Verbose "${desc} does not exist." continue } foreach ($vdir in $vdirs) { $desc = "IIS virtual directory ""$($vdir.Path)""${appDesc} under site ""$($site.Name)""" if ($vdir.Path -eq '/') { $msg = "Failed to delete ${desc} because it is the root, default virtual directory. Use the " + """${suggestedCmd}"" function instead." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } $vdirToDelete = $app.VirtualDirectories | Where-Object 'Path' -EQ $vdir.Path if (-not $vdirToDelete) { Write-Verbose "${desc} does not exist." continue } Write-Information "Deleting ${desc}." $app.VirtualDirectories.Remove($vdirToDelete) $save = $true } } } if ($save) { Save-CIisConfiguration } } function Uninstall-CIisWebsite { <# .SYNOPSIS Removes a website .DESCRIPTION Pretty simple: removes the website named `Name`. If no website with that name exists, nothing happens. .LINK Get-CIisWebsite .LINK Install-CIisWebsite .EXAMPLE Uninstall-CIisWebsite -Name 'MyWebsite' Removes MyWebsite. .EXAMPLE Uninstall-CIisWebsite 1 Removes the website whose ID is 1. #> [CmdletBinding(SupportsShouldProcess)] param( # The name or ID of the website to remove. [Parameter(Mandatory, Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]] $Name ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $sitesToDelete = [Collections.Generic.List[String]]::New() } process { $sitesToDelete.AddRange($Name) } end { $madeChanges = $false $manager = Get-CIisServerManager foreach( $siteName in $sitesToDelete ) { $site = $manager.Sites | Where-Object 'Name' -EQ $siteName if( -not $site ) { return } $action = 'Remove IIS Website' if( $PSCmdlet.ShouldProcess($siteName, $action) ) { Write-Information "Removing IIS website ""$($siteName)""." $manager.Sites.Remove( $site ) $madeChanges = $true } } if( $madeChanges ) { Save-CIisConfiguration } } } function Unlock-CIisConfigurationSection { <# .SYNOPSIS Unlocks a section in the IIS server configuration. .DESCRIPTION Some sections/areas are locked by IIS, so that websites can't enable those settings, or have their own custom configurations. This function will unlocks those locked sections. You have to know the path to the section. You can see a list of locked sections by running: C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:? Beginning with Carbon 2.0.1, this function is available only if IIS is installed. .EXAMPLE Unlock-IisConfigSection -Name 'system.webServer/cgi' Unlocks the CGI section so that websites can configure their own CGI settings. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] [CmdletBinding(SupportsShouldProcess)] param( # The path to the section to unlock. For a list of sections, run # # C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:? [Parameter(Mandatory)] [String[]] $SectionPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState foreach( $sectionPathItem in $SectionPath ) { $section = Get-CIisConfigurationSection -SectionPath $sectionPathItem $section.OverrideMode = 'Allow' Save-CIisConfiguration -Target $sectionPathItem -Action 'Unlocking IIS Configuration Section' } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } function Wait-CIisAppPoolWorkerProcess { <# .SYNOPSIS Waits for an IIS application pool to have running worker processes. .DESCRIPTION The `Wait-CIisAppPoolWorkerProcess` function waits for an IIS application pool to have running worker processes. Pass the name of the application pool to the `AppPoolName` parameter. By default, the function waits 30 seconds for there to be at least one running worker process. You can change the timeout by passing a `[TimeSpan]` object to the `Timeout` parameter. Some IIS application pools don't auto-start: IIS waits to create a worker process until a website under the application pool has received a request. In order to get an accurate record of the application pool's worker processes, this function creates a new internal server manager object for every check. If you have pending changes made by other Carbon.IIS functions, call `Save-CIisConfiguration` before calling `Wait-CIisAppPoolWorkerProcess`. .EXAMPLE Wait-CIisAppPoolWorkerProcess -AppPoolName 'www' Demonstrates how to wait for an application pool to have a running worker process by passing the application pool name to the `AppPoolName` parameter. In this example, the function will wait for the "www" application pool. .EXAMPLE Wait-CIisAppPoolWorkerProcess -AppPoolName 'www' -Timeout (New-TimeSpan -Seconds 300) Demonstrates how control how long to wait for an application pool to have a running worker process by passing a custom `[TimeSpan]` to the `TimeSpan` parameter. In this example, the function will wait 300 seconds (i.e. five minutes). #> [CmdletBinding()] param( # The name of the application pool [Parameter(Mandatory)] [String] $AppPoolName, # The total amount of time to wait for the application pool to have running worker processes. The default # timeout is 30 seconds. [TimeSpan] $Timeout = (New-TimeSpan -Seconds 30) ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $appPool = Get-CIisAppPool -Name $AppPoolName if (-not $appPool) { return } $timer = [Diagnostics.Stopwatch]::StartNew() while ($timer.Elapsed -lt $Timeout) { $mgr = Get-CIisServerManager -Reset $appPool = $mgr.ApplicationPools | Where-Object 'Name' -EQ $appPool.Name [Object[]] $wps = $appPool.WorkerProcesses [Object[]] $pss = $wps | ForEach-Object { Get-Process -Id $_.ProcessId -ErrorAction Ignore } if ($wps.Length -eq $pss.Length) { return } Start-Sleep -Milliseconds 100 } $msg = "The ""$($appPool.Name)"" IIS application pool's worker processes haven't started after waiting " + "$($Timeout)." Write-Error -Message $msg -ErrorAction $ErrorActionPreference } function Write-IisVerbose { [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the site. $SiteName, [string] $VirtualPath = '', [Parameter(Position=1)] [string] # The name of the setting. $Name, [Parameter(Position=2)] [string] $OldValue = '', [Parameter(Position=3)] [string] $NewValue = '' ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $VirtualPath ) { $SiteName = Join-CIisPath -Path $SiteName, $VirtualPath } Write-Verbose -Message ('[IIS Website] [{0}] {1,-34} {2} -> {3}' -f $SiteName,$Name,$OldValue,$NewValue) } function Write-CIisWarningOnce { [CmdletBinding(DefaultParameterSetName='Message')] param( [Parameter(ValueFromPipeline, ParameterSetName='Message')] [String] $Message, [Parameter(Mandatory, ParameterSetName='ObsoleteSiteNameAndVirtualPath')] [switch] $ForObsoleteSiteNameAndVirtualPathParameter ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'ObsoleteSiteNameAndVirtualPath') { $functionName = $PSCmdlet.MyInvocation.MyCommand.Name $caller = Get-PSCallStack | Select-Object -Skip 1 | Select-Object -First 1 if ($caller.FunctionName -like '*-CIis*') { $functionName = $caller.FunctionName } $Message = "The $($functionName) function''s ""SiteName"" and ""VirtualPath"" parameters are obsolete " + 'and have been replaced with a single "LocationPath" parameter, which should be the combined ' + 'path of the location/object to configure, e.g. ' + "``$($functionName) -LocationPath 'SiteName/Virtual/Path'``. You can also use the " + '`Join-CIisPath` function to combine site names and virtual paths into a single location path ' + "e.g. ``$($functionName) -LocationPath ('SiteName', 'Virtual/Path' | Join-CIisPath)``." } if ($script:warningMessages.ContainsKey($Message)) { return } Write-Warning -Message $Message $script:warningMessages[$Message] = $true } } # Get-Command -ModuleName doesn't work inside a module while its being imported. $carbonIisCmds = Get-ChildItem -Path 'function:' | Where-Object 'ModuleName' -EQ 'Carbon.IIS' $alwaysExclude = @{ 'Split-CIisLocationPath' = $true; 'Get-CIisCollectionKeyName' = $true; 'Write-CIisVerbose' = $true; 'Write-IisVerbose' = $true; } function Format-Argument { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [String] $InputObject ) process { # If it contains any quote characters, enclose in single quotes and escape just the single quotes. This will # handle any double quotes, backticks, and spaces. if ($_.Contains("'") -or $_.Contains('"')) { return "'$($_ -replace "'", "''")'" } # No quotes, but contains spaces, so enclose in single quotes, which will handle the spaces and any backtick # characters. if ($_.Contains(' ')) { return "'$($_)'" } # Sweet. Nothing fancy. Return the original string. return $_ } } function Register-CIisArgumentCompleter { [CmdletBinding()] param( [String] $Filter = '*', [String[]] $Exclude, [Parameter(Mandatory)] [String] $ParameterName, [String[]] $ExcludeParameterName, [Parameter(Mandatory)] [String] $Description, [Parameter(Mandatory)] [scriptblock] $ScriptBlock ) $cmdNames = $carbonIisCmds | Where-Object 'Name' -Like $Filter | Where-Object { -not $alwaysExclude.ContainsKey($_.Name) } | Where-Object { $cmd = $_ if (-not $Exclude) { return $true } $excludedMatches = $Exclude | Where-Object { $cmd.Name -like $_ } if ($excludedMatches) { return $false } return $true } | Where-Object { $_.Parameters.ContainsKey($ParameterName) } | Where-Object { $cmd = $_ if (-not $ExcludeParameterName) { return $true } foreach ($excludeFilter in $ExcludeParameterName) { foreach ($paramName in $cmd.Parameters.Keys) { if ($paramName -like $excludeFilter) { return $false } } } return $true } | Select-Object -ExpandProperty 'Name' if (-not $cmdNames) { $msg = "Found no $($Description) commands matching filter ""$($Filter)"" with a parameter named " + "$($ParameterName)." Write-Debug $msg return } Write-Debug "Registering $($Description) auto-completer on parameter ""$($ParameterName)"" for functions" $cmdNames | ForEach-Object { " * $($_)" } | Write-Debug Register-ArgumentCompleter -CommandName $cmdNames -ParameterName $ParameterName -ScriptBlock $ScriptBlock } $appPoolNameCompleter = { param( [String] $CommandName, [String] $ParameterName, [String] $WordToComplete, $CommandAst, [hashtable] $FakeBoundParameters ) Write-Debug "$($WordToComplete)" $completions = @() Get-CIisAppPool -Name "$($WordToComplete)*" -ErrorAction Ignore | Select-Object -ExpandProperty 'Name' | Tee-Object -Variable 'completions' | Format-Argument | Write-Output $completions | ForEach-Object { Write-Debug "> $($_)" } } Register-CIisArgumentCompleter -Filter '*-CIisAppPool' ` -Exclude 'Install-CIisAppPool' ` -ParameterName 'Name' ` -Description 'application pool name' ` -ScriptBlock $appPoolNameCompleter Register-CIisArgumentCompleter -Filter '*' ` -ParameterName 'AppPoolName' ` -Description 'application pool name' ` -ScriptBlock $appPoolNameCompleter $websiteNameCompleter = { param( [String] $CommandName, [String] $ParameterName, [String] $WordToComplete, $CommandAst, [hashtable] $FakeBoundParameters ) Write-Debug "$($WordToComplete)" $completions = @() Get-CIisWebsite -Name "$($WordToComplete)*" -ErrorAction Ignore | Select-Object -ExpandProperty 'Name' | Tee-Object -Variable 'completions' | Format-Argument | Write-Output $completions | ForEach-Object { Write-Debug "> $($_)" } } Register-CIisArgumentCompleter -Filter '*-CIisWebsite' ` -Exclude 'Install-CIisWebsite' ` -ParameterName 'Name' ` -Description 'website name' ` -ScriptBlock $websiteNameCompleter Register-CIisArgumentCompleter -ParameterName 'SiteName' ` -ExcludeParameterName 'LocationPath' ` -Description 'website name' ` -ScriptBlock $websiteNameCompleter $appCompleter = { param( [String] $CommandName, [String] $ParameterName, [String] $WordToComplete, $CommandAst, [hashtable] $FakeBoundParameters ) if (-not $FakeBoundParameters.ContainsKey('SiteName')) { return } if ($WordToComplete -and $WordToComplete.Length -gt 0 -and $WordToComplete[0] -ne '/') { $WordToComplete = "/$($WordToComplete)" } $completions = @() Get-CIisApplication -LocationPath (Join-CIisPath $FakeBoundParameters['SiteName'], "$($WordToComplete)*") | Select-Object -ExpandProperty 'Path' | Tee-Object -Variable 'completions' | Format-Argument | Write-Output $completions | ForEach-Object { Write-Debug "> $($_)" } } Register-CIisArgumentCompleter -ParameterName 'VirtualPath' ` -ExcludeParameterName 'LocationPath' ` -Exclude 'Install-*' ` -ScriptBlock $appCompleter ` -Description 'application virtual path' $appCompleter = { param( [String] $CommandName, [String] $ParameterName, [String] $WordToComplete, $CommandAst, [hashtable] $FakeBoundParameters ) if (-not $FakeBoundParameters.ContainsKey('SiteName')) { Write-Debug 'No SiteName' return } if ($WordToComplete -and $WordToComplete.Length -gt 0 -and $WordToComplete[0] -ne '/') { $WordToComplete = "/$($WordToComplete)" } $completions = @() Get-CIisApplication -SiteName $FakeBoundParameters['SiteName'] | Select-Object -ExpandProperty 'Path' Tee-Object -Variable 'completions' | Format-Argument | Write-Output $completions | ForEach-Object { Write-Debug "> $($_)" } } Register-CIisArgumentCompleter -Description 'virtual directory' ` -ParameterName 'VirtualPath' ` -ExcludeParameterName 'LocationPath' ` -Exclude 'Install-*' ` -ScriptBlock $appCompleter $locationCompleter = { param( [String] $CommandName, [String] $ParameterName, [String] $WordToComplete, $CommandAst, [hashtable] $FakeBoundParameters ) $ErrorActionPreference = 'Continue' # Turn off other debug messages in the locater so if we need to we can debug just what's going on in this script # block. $PSDefaultParameterValues = @{ 'ConvertTo-CIisVirtualPath:Debug' = $false; 'Join-CIisPath:Debug' = $false; 'Get-CIisWebsite:Debug' = $false; } [String] $siteName = '' $locationFilter = '*' if ($WordToComplete) { $locationFilter = "$($WordToComplete)*" | ConvertTo-CIisVirtualPath -NoLeadingSlash $siteName, $null = $WordToComplete.Split('/', 2) } Write-Debug '' Write-Debug "$($WordToComplete) -> $($locationFilter)" $physicalPathsByVirtualPath = @{} [String[]] $completions = @() & { if (-not $siteName -or -not (Test-CIIsWebsite -Name ([wildcardpattern]::Escape($siteName)))) { Write-Debug "Getting website names." Get-CIisWebsite -Name "$($siteName)*" | Select-Object -ExpandProperty 'Name' | ConvertTo-CIisVirtualPath -NoLeadingSlash | Format-Argument | Write-Output return } $site = Get-CIisWebsite -Name $siteName $siteLocationPath = $site.Name foreach ($app in $site.Applications) { $appLocationPath = $siteLocationPath if ($app.Path -ne '/') { $appLocationPath = Join-CIisPath -Path $appLocationPath, $app.Path } foreach ($vdir in $app.VirtualDirectories) { $vdirLocationPath = $appLocationPath if ($vdir.Path -ne '/') { $vdirLocationPath = Join-CIisPath -Path $vdirLocationPath, $vdir.Path } $physicalPathsByVirtualPath[$vdirLocationPath] = $vdir.PhysicalPath if ($vdirLocationPath -like $locationFilter) { Write-Debug " ~ $($vdirLocationPath)" $vdirLocationPath | Write-Output } else { Write-Debug " ! ~ $($vdirLocationPath)" } } } # In order to discover any physical paths for auto-completion, we need to break the user's input into two # parts on every slash, check if the first part is a virtual path, then check if the second part is a # physical directory under that virtual path. For example, if we have wwwroot/VDir/Dir, we need to check # if `VDir/Dir` exists under the `wwwroot` virtual path's physical path, then check if `Dir` exists under # the `wwwroot/VDir` virtual directory. $locationPath, $needle = $WordToComplete.Split('/', 2) do { if ($physicalPathsByVirtualPath.ContainsKey($locationPath)) { $physicalPath = $physicalPathsByVirtualPath[$locationPath] if ($needle) { $physicalPath = Join-Path -Path $physicalPath -ChildPath $needle } if (Test-Path -Path $physicalPath) { foreach ($dir in (Get-ChildItem -Path $physicalPath -Directory)) { Join-CIisPath -Path $locationPath, $needle, $dir.Name | Write-Output } } } if (-not $needle) { break } $rootSegment, $needle = $needle.Split('/', 2) $locationPath = Join-CIisPath $locationPath, $rootSegment } while ($true) } | Tee-Object -Variable 'completions' | Format-Argument | Write-Output $completions | ForEach-Object { Write-Debug "> $($_)" } } Register-CIisArgumentCompleter -Description 'location path' ` -ParameterName 'LocationPath' ` -ScriptBlock $locationCompleter |