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.Management.Automation 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' } Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'PSModules\Carbon.Core' -Resolve) ` -Function @('Add-CTypeData', 'Resolve-CFullPath') Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'PSModules\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 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)" } $prevPath = $Path 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) } $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-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(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_Https = 8 $HttpsFlags_NegotiateCert = 32 $HttpsFlags_RequireCert = 64 $HttpsFlags_MapCert = 128 $HttpsFlags_128Bit = 256 $intFlag = 0 $flags = @() if( $RequireHttps -or $RequireClientCertificates ) { $flags += 'Ssl' $intFlag = $intFlag -bor $HttpsFlags_Https } if( $AcceptClientCertificates -or $RequireClientCertificates ) { $flags += 'SslNegotiateCert' $intFlag = $intFlag -bor $HttpsFlags_NegotiateCert } if( $RequireClientCertificates ) { $flags += 'SslRequireCert' $intFlag = $intFlag -bor $HttpsFlags_RequireCert } if( $Require128BitHttps ) { $flags += 'Ssl128' $intFlag = $intFlag -bor $HttpsFlags_128Bit } $sectionPath = 'system.webServer/security/access' $section = Get-CIisConfigurationSection -LocationPath $LocationPath -VirtualPath $VirtualPath -SectionPath $sectionPath if( -not $section ) { return } $flags = $flags -join ',' $currentIntFlag = $section['sslFlags'] $currentFlags = @( ) if( $currentIntFlag -band $HttpsFlags_Https ) { $currentFlags += 'Ssl' } if( $currentIntFlag -band $HttpsFlags_NegotiateCert ) { $currentFlags += 'SslNegotiateCert' } if( $currentIntFlag -band $HttpsFlags_RequireCert ) { $currentFlags += 'SslRequireCert' } if( $currentIntFlag -band $HttpsFlags_MapCert ) { $currentFlags += 'SslMapCert' } if( $currentIntFlag -band $HttpsFlags_128Bit ) { $currentFlags += 'Ssl128' } if( -not $currentFlags ) { $currentFlags += 'None' } $currentFlags = $currentFlags -join ',' if( $section['sslFlags'] -ne $intFlag ) { $target = "IIS:$($section.LocationPath):$($section.SectionPath)" $infoMsg = "$($target) sslFlags $($section['sslFlags']) -> $($flags)" $section['sslFlags'] = $flags Save-CIisConfiguration -Target $target -Action 'Enable HTTPS' -Message $infoMsg } } 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-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 { $msg = 'IIS:{0}: configuration section {1} not found.' -f $LocationPath,$SectionPath Write-Error $msg -ErrorAction $ErrorActionPreference return } } 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) { try { $appHostLastWriteTimeUtc = Get-Item -Path $script:applicationHostPath | Select-Object -ExpandProperty 'LastWriteTimeUtc' Write-Debug "$(New-MessagePrefix)CommitChanges()" $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.Application])] param( # The site where the application is running. [Parameter(Mandatory, Position=0)] [String] $LocationPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $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 } $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 '/' } $modified = $false if( -not $vdir ) { [Microsoft.Web.Administration.ConfigurationElementCollection]$vdirs = $destinationApp.GetCollection() $vdir = $vdirs.CreateElement('virtualDirectory') Write-IisVerbose $SiteName -VirtualPath $vPathMsg 'virtualPath' '' $VirtualPath $vdir['path'] = $VirtualPath [void]$vdirs.Add( $vdir ) $modified = $true } if( $vdir['physicalPath'] -ne $PhysicalPath ) { Write-IisVerbose $SiteName -VirtualPath $vPathMsg 'physicalPath' $vdir['physicalPath'] $PhysicalPath $vdir['physicalPath'] = $PhysicalPath $modified = $true } if( $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 ) ) } $bindingMsgs = [Collections.Generic.List[String]]::New() foreach( $bindingToRemove in $bindingsToRemove ) { $bindingMsgs.Add("- $($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 ) { $bindingMsgs.Add("+ $($bindingToAdd.Protocol)/$($bindingToAdd.BindingInformation)") $site.Bindings.Add( $bindingToAdd.BindingInformation, $bindingToAdd.Protocol ) | Out-Null $modified = $true } $prefix = "Configuring ""$($Name)"" IIS website's bindings: " foreach( $bindingMsg in $bindingMsgs ) { Write-Information "$($prefix)$($bindingMsg)" $prefix = ' ' * $prefix.Length } [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 ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $invokation = $SourceCmdlet.MyInvocation $cmd = $invokation.MyCommand $parameterSet = $cmd.ParameterSets | Where-Object 'Name' -EQ $SourceCmdlet.ParameterSetName if( -not $parameterSet ) { $parameterSet = $cmd.ParameterSets | Where-Object 'IsDefault' -EQ $true } $cmdParameters = $invokation.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 } 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-CIisConfigurationAttribute { <# .SYNOPSIS Removes an attribute from a configuration section. .DESCRIPTION The `Remove-CIisConfigurationAttribute` function removes/deletes an attribute from a website's configuration in the IIS application host configuration file. Pass the website name to the `SiteName` parameter, the path to the configuration section from which to remove the attribute to the `SectionPath` parameter, and the name of the attribute to remove/delete to the `Name` parameter. The function deletes that attribute. If the attribute doesn't exist, nothing happens. To delete more than one attribute on a specific element at a ttime, either pass multiple names to the `Name` parameter, or pipe the list of attributes to `Remove-CIisConfigurationAttribute`. 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. .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. #> [CmdletBinding(SupportsShouldProcess)] param( # The name of the website to configure. [Parameter(Mandatory, Position=0)] [Alias('SiteName')] [String] $LocationPath, # OBSOLETE. Use the `LocationPath` parameter instead. [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)] [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 $section = Get-CIisConfigurationSection -LocationPath $LocationPath -VirtualPath $VirtualPath -SectionPath $SectionPath if( -not $section ) { return } $attrNameFieldLength = $section.Attributes | Select-Object -ExpandProperty 'Name' | Select-Object -ExpandProperty 'Length' | Measure-Object -Maximum | Select-Object -ExpandProperty 'Maximum' $nameFormat = "{0,-$($attrNameFieldLength)}" $attrNames = [Collections.Arraylist]::New() $locationPathMsg = $LocationPath if ($VirtualPath) { $locationPathMsg = Join-CIisPath -Path $LocationPath, $VirtualPath } $basePrefix = "[IIS:/Sites/$($locationPathMsg):$($SectionPath)" } process { if( -not $section ) { return } foreach( $nameItem in $Name ) { $attr = $section.Attributes[$nameItem] if( -not $attr ) { $msg = "IIS configuration section ""$($SectionPath)"" 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))" $msgPrefix = "$($basePrefix)@$($nameFormat -f $nameItem)] " Write-Debug "$($msgPrefix)$($attr.IsInheritedFromDefaultValue) $($attr.Value) $($attr.Schema.DefaultValue)" $hasDefaultValue = $attr.Value -eq $attr.Schema.DefaultValue if( -not $attr.IsInheritedFromDefaultValue -and -not $hasDefaultValue ) { [void]$attrNames.Add($nameItem) } $pathMsg = '' if( $VirtualPath ) { $pathMsg = ", path '$($VirtualPath)'" } $target = "$($nameItem) from IIS website '$($LocationPath)'$($pathMsg), configuration section '$($SectionPath)'" $action = 'Remove Attribute' if( $PSCmdlet.ShouldProcess($target, $action) ) { $msg = "Removing attribute $($target -replace '''', '"')" Write-Information $msg # Fortunately, only actually persists changes to applicationHost.config if there are any changes. $attr.Delete() } } } } 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 ) 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 = "Configuration location ""$($LocationPath)"" does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } (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 } 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 toremvoe 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 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 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 ` -Attribute @{ 'name' = $Name } ` -Exclude @('applicationPoolSid', 'state') ` -Reset:$Reset } 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 } 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 } $appPool = Get-CIisAppPool @getArgs if( -not $appPool ) { return } $currentSchedule = $appPool.Recycling.PeriodicRestart.Schedule $currentTimes = $currentSchedule | Select-Object -ExpandProperty 'Time' | Sort-Object $Schedule = $Schedule | Sort-Object $scheduleChanged = $false if( ($currentTimes -join ', ') -ne ($Schedule -join ', ') ) { $prefixMsg = "IIS ""$($AppPoolName)"" application pool: periodic restart schedule " $clearedPrefix = $false foreach( $time in (($currentTimes + $Schedule) | Select-Object -Unique) ) { $icon = ' ' $action = '' if( $Schedule -notcontains $time ) { $icon = '-' $action = 'Remove' } elseif( $currentTimes -notcontains $time ) { $icon = '+' $action = 'Add' } if( $icon -eq ' ' ) { continue } $action = "$($action) Time" $target = "$($time) for '$($AppPoolName)' IIS application pool's periodic restart schedule" if( $PSCmdlet.ShouldProcess($target, $action) ) { Write-Information "$($prefixMsg)$($icon) $($time)" $scheduleChanged = $true } if( -not $clearedPrefix ) { $prefixMsg = ' ' * $prefixMsg.Length $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 } $currentSchedule.Add($add) } Save-CIisConfiguration } } $appPool = Get-CIisAppPool @getArgs if( -not $appPool ) { return } $targetMsg = 'IIS appliation pool defaults periodic restart' if( $AppPoolName ) { $targetMsg = """$($AppPoolName)"" IIS application pool's periodic restart" } Invoke-SetConfigurationAttribute -ConfigurationElement $appPool.Recycling.PeriodicRestart ` -PSCmdlet $PSCmdlet ` -Target $targetMsg ` -Reset:$Reset } 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 } 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 } 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 ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState 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) { $locationPathMsg = '' if ($Element.LocationPath) { $locationPathMsg = " at location ""$($Element.LocationPath)""" } $msg = "Unable to set attribute ""$($Name)"" on configuration element ""$($Element.SectionPath)""" + "$($locationPathMsg) because that attribute doesn't exist on that element. Valid attributes are: " + "$(($Element.Attributes | Select-Object -ExpandProperty 'Name') -join ', ')." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $parentAttr = $null if ($parentConfigElement) { $parentAttr = $parentConfigElement.Attributes[$Name] } $defaultValue = $currentAttr.Schema.DefaultValue if ($parentAttr) { $defaultValue = $parentAttr.Value if ($parentAttr.IsInheritedFromDefaultValue) { $defaultValue = $parentAttr.Schema.DefaultValue } } $currentValue = $currentAttr.Value $protectValue = $Sensitive -or $currentAttr.Name -eq 'password' if( $Value -is [SecureString] ) { $Value = [pscredential]::New('i', $Value).GetNetworkCredential().Password $protectValue = $true } elseif( $Value -is [switch] ) { $Value = $Value.IsPresent } $appHostAttrExists = ($configElementAppHostNode -and $configElementAppHostNode.HasAttribute($Name)) $currentValueMsg = "[$($defaultValue)]" $currentValueIsDefault = $true if ($null -ne $currentValue -and -not $currentAttr.IsInheritedFromDefaultValue -and $appHostAttrExists) { $currentValueMsg = $currentValue.ToString() $currentValueIsDefault = $false } $valueMsg = "[$($defaultValue)]" if ($null -ne $Value) { $valueMsg = $Value.ToString() } if ($Value -is [Enum]) { $currentValueAsEnum = [Enum]::Parse($Value.GetType().FullName, $currentValue, $true) $currentValueMsg = "$($currentValueAsEnum) ($($currentValueAsEnum.ToString('D')))" if ($currentValueIsDefault) { $currentValueMsg = "[$($currentValueMsg)]" } $valueMsg = "$($Value) ($($Value.ToString('D')))" } elseif( $currentAttr.Schema.Type -eq 'timeSpan' -and $Value -is [UInt64] ) { $valueMsg = [TimeSpan]::New($Value) } if( $protectValue ) { $currentValueMsg = '*' * 8 $valueMsg = '*' * 8 } $msgPrefix = " @$($nameFormat -f $currentAttr.Name) " $noChangeMsg = "$($msgPrefix)$($currentValueMsg) == $($valueMsg)" $changedMsg = "$($msgPrefix)$($currentValueMsg) -> $($valueMsg)" if ($null -eq $Value) { if ($currentAttr.IsInheritedFromDefaultValue) { # do nothing Write-Debug $noChangeMsg return } if ($LocationPath ) { # The `IsInheritedFromDefaultValue` property is `$false` if the applicationHost.config defines the # element and the element attribute values are the same value as the default value. So, the only way to # know if the location we're working on doesn't have the attribute is to load the applicationHost.config # and look for the attribute. :( $xpath = "/configuration/location[@path = '$($LocationPath)']/$($Element.SectionPath)/@$($Name)" if (-not $appHostConfigXml.SelectSingleNode($xpath)) { # do nothing Write-Debug $noChangeMsg return } } # Attribute was previously supplied but now it isn't, or, attribute value changed manually. Delete # attribute so its value reverts to IIS's default value. $infoMessages.Add($changedMsg) $action = "Remove Attribute" $whatIf = "$($currentAttr.Name) for $($Target -replace '"', '''')" if( $PSCmdlet.ShouldProcess($whatIf, $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]$removedNames.Add($currentAttr.Name) } return } if ($currentValue -eq $Value) { if (-not $isConfigSection -or ($isConfigSection -and $appHostAttrExists)) { Write-Debug $noChangeMsg return } } [void]$infoMessages.Add($changedMsg) try { $ConfigurationElement.SetAttributeValue($currentAttr.Name, $Value) } 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 } } $parentConfigElement = $null if ($LocationPath) { $parentConfigElement = Get-CIisConfigurationSection -SectionPath $SectionPath } $isConfigSection = $null -ne ($ConfigurationElement | Get-Member -Name 'SectionPath') if( -not $SectionPath -and $isConfigSection ) { $SectionPath = $ConfigurationElement.SectionPath } [xml] $appHostConfigXml = Get-Content -Path $script:applicationHostPath $parentAppHostNode = $null $locationAppHostNode = $null $configElementAppHostNode = $null if ($isConfigSection) { $xpath = "/configuration/$($SectionPath)" $parentAppHostNode = $appHostConfigXml.SelectSingleNode($xpath) $configElementAppHostNode = $parentAppHostNode if ($LocationPath) { $xpath = "/configuration/location[@path = '$($LocationPath)']/$($SectionPath)" $locationAppHostNode = $appHostConfigXml.SelectSingleNode($xpath) $configElementAppHostNode = $locationAppHostNode } } $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() $removedNames = [Collections.ArrayList]::New() $infoMessages = [Collections.Generic.List[String]]::New() if( -not $Target ) { if( $SectionPath ) { $Target = $sectionPath } else { $Target = $ConfigurationElement.GetType().Name } if ($LocationPath) { $Target = "$($Target) at location $($LocationPath)" } } if ($Name) { Set-AttributeValue -Element $ConfigurationElement -Name $Name -Value $Value } else { $attrsToUpdate = $Attribute.Keys if ($Reset) { $attrsToUpdate = $ConfigurationElement.Attributes | Select-Object -ExpandProperty 'Name' } foreach ($attrName in ($attrsToUpdate | Sort-Object)) { Set-AttributeValue -Element $ConfigurationElement -Name $attrName -Value $Attribute[$attrName] } } $pluralSuffix = '' if( $updatedNames.Count -gt 1 ) { $pluralSuffix = 's' } $whatIfTarget = "$($updatedNames -join ', ') for $($Target -replace '"', '''')" $action = "Set Attribute$($pluralSuffix)" $shouldCommit = $updatedNames -and $PSCmdlet.ShouldProcess($whatIfTarget, $action) if( $shouldCommit -or $removedNames ) { if( $infoMessages.Count -eq 1 ) { $msg = "Configuring $($Target): $($infoMessages.Trim() -replace ' {2,}', ' ')" Write-Information $msg } elseif( $infoMessages.Count -gt 1) { Write-Information "Configuring $($Target)." $infoMessages | ForEach-Object { Write-Information $_ } } } # Only save if we made any changes. if( $updatedNames -or $removedNames ) { 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 } 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 } 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 } 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. .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 } $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 { $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. .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 } $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 { $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 { $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 { $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 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-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-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; '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 |