IISSiteInstall.psm1
Write-Verbose 'Importing from [C:\MyProjects\IISSiteInstall\IISSiteInstall\private]' # .\IISSiteInstall\private\ConvertTo-BuiltInUsername.ps1 function ConvertTo-BuiltInUsername { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory)] [ValidateSet('ApplicationPoolIdentity', 'LocalService', 'LocalSystem', 'NetworkService')] [string] $IdentityType, [Parameter(Mandatory)] [string] $AppPoolName ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' if ($IdentityType -eq 'ApplicationPoolIdentity' -and [string]::IsNullOrWhiteSpace($AppPoolName)) { throw "IdentityType of 'ApplicationPoolIdentity' requires that an -AppPoolName" } } process { try { switch ($IdentityType) { 'ApplicationPoolIdentity' { "IIS AppPool\$AppPoolName" } 'NetworkService' { 'NT AUTHORITY\NETWORK SERVICE' } 'LocalSystem' { 'NT AUTHORITY\SYSTEM' } 'LocalService' { 'NT AUTHORITY\LOCAL SERVICE' } Default { throw "IdentityType '$IdentityType' does not represent a built-in user" } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\private\Get-IISSiteAclPathHelper.ps1 function Get-IISSiteAclPathHelper { [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] [string] $Name, [switch] $Recurse ) begin { Set-StrictMode -Version 'Latest' $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allSiteInfos = Get-IISSiteHierarchyInfo $tempAspNetFilesPaths = Get-CaccaTempAspNetFilesPath } process { try { $siteInfos = if ([string]::IsNullOrWhiteSpace($Name)) { $allSiteInfos } else { $allSiteInfos | Where-Object Site_Name -eq $Name } $siteNames = @() $siteNames += $siteInfos | Select-Object -Exp Site_Name -Unique foreach ($siteName in $siteNames) { $siteInfo = $siteInfos | Where-Object Site_Name -eq $siteName $otherSiteInfos = $allSiteInfos | Where-Object Site_Name -ne $siteName $candidatePaths = @() $sitePaths = @() $sitePaths += $siteInfo | Select-Object -Exp App_PhysicalPath | Where-Object { Test-Path $_ } $siteSubPaths = @() if ($Recurse) { $excludedPaths = @() $excludedPaths += $otherSiteInfos | Select-Object -Exp App_PhysicalPath | ForEach-Object { Join-Path $_ '\*' } # note: excluding node_modules for perf reasons (hopefully no site adds permissions to specific node modules!) $siteSubPaths += Get-ChildItem $sitePaths -Recurse -Directory -Depth 5 -Exclude 'node_modules' | Select-Object -Exp FullName -PV candidatePath | Where-Object { $excludedPaths.Count -eq 0 -or $excludedPaths.Where( { (Join-Path $candidatePath '\') -NotLike $_}) } } $uniqueFolderPaths = $sitePaths + $siteSubPaths | Select-Object -Unique $siteFilePaths = @() if ($Recurse) { $siteFilePaths += @('*.bat', '*.exe', '*.ps1') | ForEach-Object { Get-ChildItem $uniqueFolderPaths -Recurse -File -Depth 5 -Filter $_ | Select-Object -Exp FullName } } $uniqueSitePaths = $uniqueFolderPaths + $siteFilePaths | Select-Object -Unique $candidatePaths += $uniqueSitePaths $candidatePaths += $tempAspNetFilesPaths $appPoolUsernames = @() $appPoolUsernames += $siteInfo | Select-Object -Exp AppPool_Username | Select-Object -Unique foreach ($username in $appPoolUsernames) { $candidatePaths | ForEach-Object { $path = $_ $select = @( @{n = 'SiteName'; e = {$siteName}}, @{n = 'Path'; e = {$path}}, @{n = 'IdentityReference'; e = {$username}} ) (Get-Item $path).GetAccessControl('Access').Access | Where-Object { $_.IsInherited -eq $false -and $_.IdentityReference -eq $username } | Select-Object $select } } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\private\Get-IISSiteBackConnectionHelper.ps1 function Get-IISSiteBackConnectionHelper { [CmdletBinding(DefaultParameterSetName = 'None')] param ( [Parameter(ValueFromPipeline, ParameterSetName = 'Name', Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipeline, ParameterSetName = 'Object', Position = 0)] [Microsoft.Web.Administration.Site] $InputObject ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allHostnameEntries = @(Get-TecBoxBackConnectionHostNames) | ForEach-Object { [PsCustomObject] @{ Hostname = $_ } } } process { try { if (![string]::IsNullOrWhiteSpace($Name)) { $Name = $Name.Trim() } $selectedSites = @() $selectedSites += if ($InputObject) { $InputObject } elseif (![string]::IsNullOrWhiteSpace($Name)) { Get-IISSite $Name } else { Get-IISSite } foreach ($site in $selectedSites) { $siteEntries = $site.Bindings | Select-Object -Exp Host -Unique | Select-object @{n = 'Hostname'; e = { $_ }}, @{n = 'SiteName'; e = { $site.Name }} $siteEntries | ForEach-Object { $siteEntry = $_ $allHostnameEntries | Where-Object { $_.Hostname -eq $siteEntry.Hostname } | Select-Object -Property *, @{ n = 'SiteName'; e = { $siteEntry.SiteName } } } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\private\Get-IISSiteHostsFileEntryHelper.ps1 function Get-IISSiteHostsFileEntryHelper { [CmdletBinding(DefaultParameterSetName = 'None')] param ( [Parameter(ValueFromPipeline, ParameterSetName = 'Name', Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipeline, ParameterSetName = 'Object', Position = 0)] [Microsoft.Web.Administration.Site] $InputObject ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allHostnameEntries = @() $allHostnameEntries += Get-TecBoxHostnames } process { try { if (![string]::IsNullOrWhiteSpace($Name)) { $Name = $Name.Trim() } $selectedSites = @() $selectedSites += if ($InputObject) { $InputObject } elseif (![string]::IsNullOrWhiteSpace($Name)) { Get-IISSite $Name } else { Get-IISSite } foreach ($site in $selectedSites) { $siteEntries = $site.Bindings | Select-Object -Exp Host -Unique | Select-object @{n = 'Hostname'; e = { $_ }}, @{n = 'SiteName'; e = { $site.Name }} $siteEntries | ForEach-Object { $siteEntry = $_ $allHostnameEntries | Where-Object { $_.Hostname -eq $siteEntry.Hostname } | Select-Object -Property *, @{ n = 'SiteName'; e = { $siteEntry.SiteName } } } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\private\GetAppPoolOtherSiteCount.ps1 function GetAppPoolOtherSiteCount { [CmdletBinding()] param ( [string] $ExcludeSiteName, [string] $AppPoolName ) begin { Set-StrictMode -Version 'Latest' $ErrorActionPreference = 'Stop' } process { Get-IISSiteHierarchyInfo | Where-Object { $_.AppPool_Name -eq $AppPoolName -and $_.Site_Name -ne $ExcludeSiteName } | Select-Object Site_Name -Unique | Measure-Object | Select-Object -Exp Count } } # .\IISSiteInstall\private\Restart-AppPool.ps1 function Restart-AppPool { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="Wrapper function for mocking")] param ([string] $Name) (Get-IISAppPool $Name).Recycle() } Write-Verbose 'Importing from [C:\MyProjects\IISSiteInstall\IISSiteInstall\public]' # .\IISSiteInstall\public\Add-IISSitePort.ps1 function Add-IISSitePort { <# .SYNOPSIS Adds a port number to the existing collection of IIS site bindings .DESCRIPTION Adds a port number to the existing collection of IIS site bindings. .PARAMETER Name Name of an IIS Website .PARAMETER Port Port number to add .EXAMPLE Add-CaccaIISSitePort Series5 -Port 8080 .NOTES The port number will be skipped for each existing endpoint that already binds that port #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Name, [ValidateRange(0, 65535)] [int] $Port ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' function GetIISBinding { param([string]$SiteName) $list = Get-IISSiteBinding $SiteName # "unwrap" BindingCollection to make Binding object's easier to consume via # the PS pipeline foreach ($item in $list) { $item } } } process { try { $existing = GetIISBinding $Name $portNeutralBindingInfo = $existing | ForEach-Object { $host = $_.Host $ip = $_.EndPoint.Address.ToString() if ($ip -eq '0.0.0.0') { $ip = '*' } "$($ip):{0}:$host" } | Select-Object -Unique $candidateBindingInfo = $portNeutralBindingInfo | ForEach-Object { $_ -f $Port } $existingBindingInfo = $existing | Select-Object -Exp BindingInformation $newBinding = $candidateBindingInfo | Where-Object { $_ -notin $existingBindingInfo } | Select-Object @{ n = 'Name'; e = {$Name} }, @{ n = 'BindingInformation'; e = {$_} } $newBinding | New-IISSiteBinding } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Get-IISAppPoolUsername.ps1 function Get-IISAppPoolUsername { <# .SYNOPSIS Returns the Username used as the Identity for an IIS AppPool .DESCRIPTION Returns the Username used as the Identity for an IIS AppPool. This will either be the domain qualified name of a specific Windows User account or the qualified name of the built-in accounts (eg 'NT Authority\System'), which ever is assigned as the Identity of the AppPool .PARAMETER InputObject The AppPool to return a username .EXAMPLE Get-IISAppPool DefaultAppPool | Get-CaccaIISAppPoolUsername #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [Microsoft.Web.Administration.ApplicationPool] $InputObject ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { try { ConvertTo-BuiltInUsername ($InputObject.ProcessModel.IdentityType) ($InputObject.Name) } catch { $InputObject.ProcessModel.UserName } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Get-IISSiteAclPath.ps1 function Get-IISSiteAclPath { <# .SYNOPSIS Return the file/folder paths where the identity of the AppPool(s) used by the IIS site has been assigned one or more file/folder permission .DESCRIPTION Return the file/folder paths where the identity of the AppPool(s) used by the IIS site has been assigned one or more file/folder permission. The physical path of the website along with any child applications will be inspected. Optionally, supply the name of a IIS Website to return just those paths for a single site. Optionally, supply -Recurse to search all nested paths under the physical paths inspected. Items in the output have a 'IsShared' property. A path is considered shared if it has a permission assigned to an Identity that is used on more than one IIS website. This 'IsShared' property will typically be used to determine which file permissions can be safely removed when an IIS Website is removed .PARAMETER Name Name of an IIS Website .PARAMETER Recurse Search subfolders .EXAMPLE Get-CaccaIISSiteAclPath Series5 -Recurse SiteName Path IdentityReference IsShared -------- ---- ----------------- -------- Series5 C:\inetpub\sites\Series5 IIS AppPool\Series5-AppPool False Series5 C:\Git\Series5\src\Ram.Series5.Spa IIS AppPool\Series5-AppPool False Series5 C:\Git\Series5\src\Ram.Series5.WinLogin IIS AppPool\Series5-AppPool False Series5 C:\Git\Series5\src\Ram.Series5.Spa\App_Data IIS AppPool\Series5-AppPool False Series5 C:\Git\Series5\src\Ram.Series5.WinLogin\App_Data IIS AppPool\Series5-AppPool False Series5 C:\Windows\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files IIS AppPool\Series5-AppPool False Series5 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files IIS AppPool\Series5-AppPool False .NOTES Currently, Virtual Directories will not be inspected #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string] $Name, [switch] $Recurse ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allSiteInfos = @() $allSiteInfos += Get-IISSiteAclPathHelper } process { try { if (![string]::IsNullOrWhiteSpace($Name)) { $Name = $Name.Trim() } $siteInfos = if ([string]::IsNullOrWhiteSpace($Name)) { $allSiteInfos } else { $allSiteInfos | Where-Object SiteName -eq $Name } $siteNames = @() $siteNames += $siteInfos | Select-Object -Exp SiteName -Unique foreach ($siteName in $siteNames) { $siteAclPaths = Get-IISSiteAclPathHelper $siteName -Recurse:$Recurse $otherSiteAclPaths = $allSiteInfos | Where-Object SiteName -ne $siteName Write-Debug "Acl Paths: $siteAclPaths" $siteAclPaths | ForEach-Object { $path = $_.Path $identityReference = $_.IdentityReference $isShared = ($otherSiteAclPaths | Where-Object { $_.Path -eq $path -and $_.IdentityReference -eq $identityReference } | Measure-Object).Count -ne 0 $_ | Select-Object -Property *, @{ n='IsShared'; e={$isShared}} } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Get-IISSiteBackConnection.ps1 function Get-IISSiteBackConnection { <# .SYNOPSIS Gets the list of host names that are assigned to IIS Site bindings that also appear in the BackConnectionHostNames registry value .DESCRIPTION The BackConnectionHostNames registry value is used to bypass the loopback security check for specific host names. This function returns those host names in the BackConnectionHostNames registry value that are assigned to IIS Site bindings. Items in the output have a 'IsShared' property. A hostname is considered shared if it assigned to bindings on more than one Website. This 'IsShared' property will typically be used to determine which host names can be safely removed from the BackConnectionHostNames list when an IIS Website is removed .PARAMETER Name The name of the IIS Website to filter results .PARAMETER InputObject The instance the IIS Website to filter results .EXAMPLE Get-CaccaIISSiteBackConnection Series5 Hostname SiteName IsShared -------- -------- -------- local-series5 Series5 False #> [CmdletBinding(DefaultParameterSetName = 'Name')] param ( [Parameter(ValueFromPipeline, ParameterSetName = 'Name', Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipeline, ParameterSetName = 'Object', Position = 0)] [Microsoft.Web.Administration.Site] $InputObject ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allEntries = @() $allEntries += Get-IISSiteBackConnectionHelper } process { try { $selectedSites = @() $selectedSites += if ($InputObject) { $InputObject } elseif (![string]::IsNullOrWhiteSpace($Name)) { Get-IISSite $Name } else { Get-IISSite } $siteEntries = @($selectedSites | Get-IISSiteBackConnectionHelper) $siteEntries | ForEach-Object { $entry = $_ $isShared = ($allEntries | Where-Object Hostname -eq $entry.Hostname | Select-Object SiteName -Unique | Measure-Object).Count -gt 1 $entry | Select-Object -Property *, @{ n='IsShared'; e={ $isShared }} } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Get-IISSiteHierarchyInfo.ps1 function Get-IISSiteHierarchyInfo { <# .SYNOPSIS Returns the physical paths and the User Identity assigned to IIS websites and their child Applications .DESCRIPTION Returns the physical paths and the User Identity assigned to IIS websites and their child Applications .PARAMETER Name The name of the IIS Website to filter results .PARAMETER AppName The name of the IIS Web application to filter results .EXAMPLE Get-CaccaIISSiteHierarchyInfo Series5 Output ------ Site_Name : Series5 App_Path : / App_PhysicalPath : C:\inetpub\sites\Series5 AppPool_Name : Series5-AppPool AppPool_IdentityType : ApplicationPoolIdentity AppPool_Username : IIS AppPool\Series5-AppPool Site_Name : Series5 App_Path : /Spa App_PhysicalPath : C:\Git\Series5\src\Ram.Series5.Spa AppPool_Name : Series5-AppPool AppPool_IdentityType : ApplicationPoolIdentity AppPool_Username : IIS AppPool\Series5-AppPool #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string] $AppName ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { $siteParams = if ([string]::IsNullOrWhiteSpace($Name)) { @{} } else { @{ Name = $Name.Trim() } } if (![string]::IsNullOrWhiteSpace($AppName) -and !$AppName.StartsWith('/')) { $AppName = '/' + $AppName } Get-IISSite @siteParams -PV site -WA SilentlyContinue | Select-Object -Exp Applications -PV app | Where-Object { !$AppName -or $_.Path -eq $AppName } | ForEach-Object { $existingPool = Get-IISAppPool -Name $_.ApplicationPoolName -WA SilentlyContinue if (!$existingPool) { '' } else { $existingPool } } -PV pool | Select-Object ` @{n = 'Site_Name'; e = {$site.Name}}, @{n = 'App_Path'; e = {$app.Path}}, @{n = 'App_PhysicalPath'; e = {$app.VirtualDirectories[0].PhysicalPath}}, @{n = 'AppPool_Name'; e = { if ($pool) { $app.ApplicationPoolName } }}, @{n = 'AppPool_IdentityType'; e = { if ($pool) { $pool.ProcessModel.IdentityType} }}, @{n = 'AppPool_Username'; e = { if ($pool) { Get-IISAppPoolUsername $pool } }} } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Get-IISSiteHostsFileEntry.ps1 function Get-IISSiteHostsFileEntry { <# .SYNOPSIS Gets the list of host names that are assigned to IIS Site bindings that also appear in the Windows hosts file .DESCRIPTION The hosts file (C:\Windows\System32\drivers\etc\hosts) is used to map hostnames to IP addresses instead of using a DNS server to provide that resolution. This function returns those host names in the hosts file that are assigned to IIS Site bindings. Items in the output have a 'IsShared' property. A hostname is considered shared if it assigned to bindings on more than one Website. This 'IsShared' property will typically be used to determine which host names can be safely removed from the hosts file when an IIS Website is removed .PARAMETER Name The name of the IIS Website to filter results .PARAMETER InputObject The instance the IIS Website to filter results .EXAMPLE Get-CaccaIISSiteHostsFileEntry Series5 IpAddress Hostname SiteName IsShared --------- -------- -------- -------- 127.0.0.1 local-series5 Series5 False #> [CmdletBinding(DefaultParameterSetName = 'Name')] param ( [Parameter(ValueFromPipeline, ParameterSetName = 'Name', Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipeline, ParameterSetName = 'Object', Position = 0)] [Microsoft.Web.Administration.Site] $InputObject ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $allEntries = @() $allEntries += Get-IISSiteHostsFileEntryHelper } process { try { $selectedSites = @() $selectedSites += if ($InputObject) { $InputObject } elseif (![string]::IsNullOrWhiteSpace($Name)) { Get-IISSite $Name } else { Get-IISSite } $siteEntries = @($selectedSites | Get-IISSiteHostsFileEntryHelper) $siteEntries | ForEach-Object { $entry = $_ $isShared = ($allEntries | Where-Object Hostname -eq $entry.Hostname | Select-Object SiteName -Unique | Measure-Object).Count -gt 1 $entry | Select-Object -Property *, @{ n='IsShared'; e={ $isShared }} } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\New-IISAppPool.ps1 #Requires -RunAsAdministrator function New-IISAppPool { <# .SYNOPSIS Creates a new IIS application pool .DESCRIPTION Creates a new IIS application pool .PARAMETER Name The name of the pool .PARAMETER Config A script block that will receive the instance of the pool being created .PARAMETER Force Overwrite any existing pool? .PARAMETER Commit Save changes to IIS immediately? Defaults to true .EXAMPLE New-CaccaIISAppPool MyNewPool Description ----------- Create pool using the defaults configured for all application pools. The exception to the defaults is 'Enable32BitAppOnWin64' is set to $true (best practice) .EXAMPLE New-CaccaIISAppPool MyNewPool -Config { $_.Enable32BitAppOnWin64 = $false } Description ----------- Configures the pool being created with custom settings .EXAMPLE New-CaccaIISAppPool $tempAppPool -Config { $_ | Set-CaccaIISAppPoolUser -IdentityType NetworkService -Commit:$false } Description ----------- Create the pool with an identity assigned to the Network Service built-in account .EXAMPLE $pswd = ConvertTo-SecureString '(mypassword)' -AsPlainText -Force $creds = [PsCredential]::new("$($env:COMPUTERNAME)\MyLocalUser", $pswd) New-CaccaIISAppPool $tempAppPool -Config { $_ | Set-CaccaIISAppPoolUser $creds -Commit:$false } Description ----------- Create the pool with an identity assigned to a specific user account #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [scriptblock] $Config, [switch] $Force, [switch] $Commit ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' if (!$PSBoundParameters.ContainsKey('Commit')) { $Commit = $true } } process { try { Write-Information "Create app pool '$Name'" if ($null -eq $Config) { $Config = {} } [Microsoft.Web.Administration.ServerManager] $manager = Get-IISServerManager $existingPool = $manager.ApplicationPools[$Name] if (!$Force -and $existingPool) { throw "App pool '$Name' already exists. Supply -Force to overwrite" } if ($Commit) { Start-IISCommitDelay } try { if ($existingPool -and $PSCmdlet.ShouldProcess($Name, 'Remove App pool')) { # note: not using Remove-IISAppPool as do NOT want to remove file permissions $manager.ApplicationPools.Remove($existingPool) } if ($PSCmdlet.ShouldProcess($Name, 'Create App pool')) { [Microsoft.Web.Administration.ApplicationPool] $pool = $manager.ApplicationPools.Add($Name) # todo: do NOT set this when it's detected that OS is 64bit onlys $pool.Enable32BitAppOnWin64 = $true # this IS the recommended default even for 64bit servers $pool | ForEach-Object $Config } if ($Commit) { Stop-IISCommitDelay } } catch { if ($Commit) { Stop-IISCommitDelay -Commit:$false } throw } finally { if ($Commit) { # make sure subsequent scripts will not fail because the ServerManger is now readonly Reset-IISServerManager -Confirm:$false } } Get-IISAppPool $Name } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\New-IISWebApp.ps1 #Requires -RunAsAdministrator function New-IISWebApp { <# .SYNOPSIS Creates a new IIS Web application + App Pool, assigning it least-privilege file permissions .DESCRIPTION Creates a new IIS Web application + App Pool, setting least privilege file permissions to the useraccount configured as the identity of the IIS AppPool. These bare minium file permissions include: - Path: Read 'This folder', file and subfolder permissions (inherited) - Temporary ASP.NET Files: Read 'This folder', file and subfolder permissions (inherited) - ModifyPaths: modify 'This folder', file and subfolder permissions (inherited) - ExecutePaths: read+execute file (no inherit) .PARAMETER SiteName The name of the IIS Website to add the application to .PARAMETER Name The logical path name of the application (eg MyApp, /MyApp/NestedApp) .PARAMETER Path The physcial path of the application. Defaults to using 'Name' as a sub folder of the physical site path. Path will be created if missing. .PARAMETER Config A script block that will receive the instance of the application being created .PARAMETER AppPoolName The name of the AppPool to assign and create if missing. If not supplied, will default to use the AppPool of the IIS Website .PARAMETER AppPoolConfig A script block that will receive the instance of the pool to be used by the application .PARAMETER ModifyPaths Additional paths to grant modify (inherited) permissions. Path(s) relative to 'Path' can be supplied .PARAMETER ExecutePaths Additional paths to grant read+excute permissions. Path(s) relative to 'Path' can be supplied .PARAMETER Force Overwrite any existing application? .EXAMPLE New-CaccaIISWebApp MySite MyNewApp Description ----------- Create child Web application of MySite, with the physical path set as a subfolder of the Website (eg C:\inetpub\sites\MySite\MyNewApp), and an App Pool assigned to that of the Website .EXAMPLE New-CaccaIISWebApp MySite MyNewApp -AppPoolName MyNewPool -AppPoolConfig { $_ | Set-CaccaIISAppPoolUser -IdentityType ApplicationPoolIdentity -Commit:$false } Description ----------- As above except assigns, creating as necessary, an App Pool named 'MyNewPool' and configuring that pool to use the ApplicationPoolIdentity as it's identity .EXAMPLE New-CaccaIISWebApp MySite MyNewApp C:\Some\Path\Else -Config { Unlock-CaccaIISAnonymousAuth -Location "$SiteName$($_.Path)" -Commit:$false } Description ----------- Create child Web application of MySite, with the physical path set to C:\Some\Path\Else. Uses -Config to supply a script block to perform custom configuration of the application. In this example, using the Unlock-CaccaIISAnonymousAuth cmdlet from the IISConfigUnlock module .EXAMPLE New-CaccaIISWebApp MySite MyNewApp -ModifyPaths 'App_Data', 'logs' -ExecutePaths bin\Some.exe Description ----------- Configures additional file permissions to the useraccount configured as the identity of the IIS AppPool .NOTES Exception thrown when: * Application 'Name' already exists and -Force is NOT supplied * The Application Pool 'AppPoolName' is used by another Website * Using 'AppPoolConfig' to configure an Application pool that is already assigned to another Application and/or Website #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string] $SiteName, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string] $Path, [Parameter(ValueFromPipelineByPropertyName)] [scriptblock] $Config, [Parameter(ValueFromPipelineByPropertyName)] [string] $AppPoolName, [Parameter(ValueFromPipelineByPropertyName)] [scriptblock] $AppPoolConfig, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ModifyPaths, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ExecutePaths, [switch] $Force ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' function GetAppPoolOtherAppCount { param ( [string] $SiteName, [string] $ThisAppName, [string] $AppPoolName ) Get-IISSiteHierarchyInfo | Where-Object { $_.AppPool_Name -eq $AppPoolName -and $_.Site_Name -eq $SiteName -and $_.App_Path -ne $ThisAppName } | Select-Object App_Path -Unique | Measure-Object | Select-Object -Exp Count } } process { try { Write-Information "Create Web application '$Name' under site '$SiteName'" $SiteName = $SiteName.Trim() $Name = $Name.Trim() if (!$Name.StartsWith('/')) { $Name = '/' + $Name } if ($null -eq $Config) { $Config = {} } if ($null -eq $ModifyPaths) { $ModifyPaths = @() } if ($null -eq $ExecutePaths) { $ExecutePaths = @() } $site = Get-IISSite $SiteName if (!$site) { return } $qualifiedAppName = "$SiteName$Name" $existingApp = $site.Applications[$Name] if ($existingApp -and !$Force) { throw "Web Application '$qualifiedAppName' already exists. To overwrite you must supply -Force" } $rootApp = $site.Applications['/'] if ([string]::IsNullOrWhiteSpace($AppPoolName)) { $AppPoolName = $rootApp.ApplicationPoolName } if ((GetAppPoolOtherSiteCount $SiteName $AppPoolName) -gt 0) { throw "Cannot create Web Application - AppPool '$AppPoolName' is in use on another site" } if ($AppPoolConfig -and (GetAppPoolOtherAppCount $SiteName $Name $AppPoolName) -gt 0) { throw "Cannot configure AppPool '$AppPoolName' - it belongs to another Web Application and/or this site" } $childPath = if ([string]::IsNullOrWhiteSpace($Path)) { $sitePath = $rootApp.VirtualDirectories['/'].PhysicalPath Join-Path $sitePath $Name } else { $Path } $isPathExists = Test-Path $childPath if (!$isPathExists -and $PSCmdlet.ShouldProcess($childPath, 'Create Web Application physical path')) { New-Item $childPath -ItemType Directory -WhatIf:$false | Out-Null } if ($existingApp) { Write-Information "Existing Web application '$Name' found" Remove-IISWebApp $SiteName $Name -ModifyPaths $ModifyPaths -ExecutePaths $ExecutePaths } # Remove-IISWebApp has just committed changes making our $site instance read-only, therefore fetch another one $site = Get-IISSite $SiteName Start-IISCommitDelay try { $pool = Get-IISAppPool $AppPoolName -WA SilentlyContinue if (!$pool) { $pool = New-IISAppPool $AppPoolName -Commit:$false } if ($AppPoolConfig -and $WhatIfPreference -eq $false) { $pool | ForEach-Object $AppPoolConfig | Out-Null } if ($PSCmdlet.ShouldProcess($qualifiedAppName, 'Create Web Application')) { $app = $site.Applications.Add($Name, $childPath) $app.ApplicationPoolName = $AppPoolName $app | ForEach-Object $Config } Stop-IISCommitDelay } catch { Stop-IISCommitDelay -Commit:$false throw } finally { Reset-IISServerManager -Confirm:$false } if ($WhatIfPreference -eq $true -and !$isPathExists) { # Set-CaccaIISSiteAcl requires path to exist } else { $appPoolIdentity = Get-IISAppPool $AppPoolName | Get-IISAppPoolUsername if ($WhatIfPreference -eq $true -and [string]::IsNullOrWhiteSpace($appPoolIdentity)) { $appPoolIdentity = "IIS AppPool\$AppPoolName" } $appAclParams = @{ AppPath = $childPath AppPoolIdentity = $appPoolIdentity ModifyPaths = $ModifyPaths ExecutePaths = $ExecutePaths CreateMissingPath = $true } Set-CaccaIISSiteAcl @appAclParams } (Get-IISSite $SiteName).Applications[$Name] } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\New-IISWebsite.ps1 #Requires -RunAsAdministrator function New-IISWebsite { <# .SYNOPSIS Creates a new IIS Website + App Pool, assigning it least-privilege file permissions .DESCRIPTION Creates a new IIS Web application + App Pool, setting least privilege file permissions to the useraccount configured as the identity of the IIS AppPool. These bare minium file permissions include: - Path: Read 'This folder', file and subfolder permissions (inherited) - Note: use 'SiteShellOnly' to reduce these permissions to just the folder and files but NOT subfolders - Temporary ASP.NET Files: Read 'This folder', file and subfolder permissions (inherited) - ModifyPaths: modify 'This folder', file and subfolder permissions (inherited) - ExecutePaths: read+execute file (no inherit)s .PARAMETER Name The name of the IIS Website to add the application to .PARAMETER Path The physcial path of the Website. Defaults to using "C:\inetpub\sites\$Name". Path will be created if missing. .PARAMETER Port The port number to use for the default site binding. Defaults to 80 .PARAMETER Protocol The protocol to use for the default site binding. Defaults to 'http' .PARAMETER HostName Optional hostname to use for the default site binding. See also 'HostsFileIPAddress' and 'AddHostToBackConnections' parameters .PARAMETER Config A script block that will receive the instance of the Website being created .PARAMETER ModifyPaths Additional paths to grant modify (inherited) permissions. Path(s) relative to 'Path' can be supplied .PARAMETER ExecutePaths Additional paths to grant read+excute permissions. Path(s) relative to 'Path' can be supplied .PARAMETER SiteShellOnly Grant permissions used for 'Path' to only that folder and it's files but NOT subfolders .PARAMETER AppPoolName The name of the AppPool to assign and create if missing. Defaults to "$Name-AppPool" .PARAMETER AppPoolConfig A script block that will receive the instance of the pool to be used by the application .PARAMETER HostsFileIPAddress Resolve hostname(s) used by the site bindings to an IP address (stores a record in the hosts file on this computer) .PARAMETER AddHostToBackConnections Register hostname(s) used by the site bindings as a BackConnection registry entry to bypass the loopback security check for this name(s) .PARAMETER Force Overwrite any existing Website? .EXAMPLE New-CaccaIISWebsite MySite Description ----------- Create a Website named MySite, with the physical path set to C:\inetpub\sites\MySite. Assigns an App Pool named MySite-AppPool, creating the pool if not already present. Binds the site to port 80 over http .EXAMPLE New-CaccaIISWebsite MySite -AppPoolName MyNewPool -AppPoolConfig { $_ | Set-CaccaIISAppPoolUser -IdentityType ApplicationPoolIdentity -Commit:$false } Description ----------- As above except assigns, creating as necessary, an App Pool named 'MyNewPool' and configuring that pool to use the ApplicationPoolIdentity as it's identity .EXAMPLE New-CaccaIISWebsite MySite C:\Some\Path\Else -Config { Unlock-CaccaIISAnonymousAuth -Location $_.Name -Commit:$false } Description ----------- Create Website named MySite, with the physical path set to C:\Some\Path\Else. Uses -Config to supply a script block to perform custom configuration of the Website. In this example, using the Unlock-CaccaIISAnonymousAuth cmdlet from the IISConfigUnlock module .EXAMPLE New-CaccaIISWebsite MySite -AppPoolName MyNewPool -AppPoolConfig { $_ | Set-CaccaIISAppPoolUser -IdentityType ApplicationPoolIdentity -Commit:$false } Description ----------- As above except assigns, creating as necessary, an App Pool named 'MyNewPool' and configuring that pool to use the ApplicationPoolIdentity as it's identity .EXAMPLE New-CaccaIISWebsite MySite -ModifyPaths 'App_Data', 'logs' -ExecutePaths bin\Some.exe Description ----------- Configures additional file permissions to the useraccount configured as the identity of the IIS AppPool .EXAMPLE New-CaccaIISWebsite MySite -HostsFileIPAddress 127.0.0.1 -Hostname dev-mysite -AddHostToBackConnections -Config { New-IISSiteBinding $_.Name ':8080:local-mysite' http } Description ----------- Configures the site with an additional binding to port 8080, host name 'local-mysite'. Ensures 'dev-mysite' and 'local-mysite' resolve to 127.0.0.1 on this computer whilst ensuring these host names bypass the loopback security check .NOTES Exception thrown when: * Website 'Name' already exists and -Force is NOT supplied * The Application Pool 'AppPoolName' is used by another Website #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string] $Path, [ValidateRange(0, 65535)] [int] $Port = 80, [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('http', 'https')] [string] $Protocol = 'http', [Parameter(ValueFromPipelineByPropertyName)] [string] $HostName, [Parameter(ValueFromPipelineByPropertyName)] [scriptblock] $Config, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ModifyPaths, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ExecutePaths, [switch] $SiteShellOnly, [Parameter(ValueFromPipelineByPropertyName)] [string] $AppPoolName, [Parameter(ValueFromPipelineByPropertyName)] [scriptblock] $AppPoolConfig, [string] $HostsFileIPAddress, [switch] $AddHostToBackConnections, [switch] $Force ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { Write-Information "Create website '$Name'" # reload the ServerManager instance inside of the IISAdministration module so as to avoid # concurrency failures where changes have been made via IIS Manager Reset-IISServerManager -Confirm:$false $Name = $Name.Trim() if ([string]::IsNullOrWhiteSpace($Path)) { $Path = "C:\inetpub\sites\$Name" } if ($null -eq $Config) { $Config = {} } if ($null -eq $ModifyPaths) { $ModifyPaths = @() } if ($null -eq $ExecutePaths) { $ExecutePaths = @() } if ([string]::IsNullOrWhiteSpace($AppPoolName)) { $AppPoolName = "$Name-AppPool" } if ($null -eq $AppPoolConfig) { $AppPoolConfig = {} } $existingSite = Get-IISSite $Name -WA SilentlyContinue if ($existingSite -and !$Force) { throw "Cannot create site - site '$Name' already exists. To overwrite you must supply -Force" } if ((GetAppPoolOtherSiteCount $Name $AppPoolName) -gt 0) { throw "Cannot create site - AppPool '$AppPoolName' is in use on another site" } $isPathExists = Test-Path $Path if (!$isPathExists -and $PSCmdlet.ShouldProcess($Path, 'Create Web Site physical path')) { New-Item $Path -ItemType Directory -WhatIf:$false | Out-Null } if ($existingSite) { Write-Information "Existing website '$Name' found" Remove-IISWebsite $Name -Confirm:$false } Start-IISCommitDelay try { $pool = Get-IISAppPool $AppPoolName -WA SilentlyContinue if (!$pool) { $pool = New-IISAppPool $AppPoolName -Commit:$false } if ($AppPoolConfig -and $WhatIfPreference -eq $false) { $pool | ForEach-Object $AppPoolConfig | Out-Null } $site = $null if ($PSCmdlet.ShouldProcess($Name, 'Create Web Site')) { $bindingInfo = "*:$($Port):$($HostName)" [Microsoft.Web.Administration.Site] $site = New-IISSite $Name $Path $bindingInfo $Protocol -Passthru $site.Applications["/"].ApplicationPoolName = $AppPoolName $site | ForEach-Object $Config $allHostNames = $site.Bindings | Select-Object -Exp Host -Unique if (![string]::IsNullOrWhiteSpace($HostsFileIPAddress) -and $PSCmdlet.ShouldProcess($allHostNames, 'Add hosts file entry')) { Write-Information "Add '$allHostNames' to hosts file" $allHostNames | Add-TecBoxHostnames -IPAddress $HostsFileIPAddress } if ($AddHostToBackConnections -and $PSCmdlet.ShouldProcess($allHostNames, 'Add back connection')) { Write-Information "Add '$allHostNames' to backconnection registry value" $allHostNames | Add-TecBoxBackConnectionHostNames } } Stop-IISCommitDelay } catch { Stop-IISCommitDelay -Commit:$false throw } finally { Reset-IISServerManager -Confirm:$false } if ($WhatIfPreference -eq $true -and !$isPathExists) { # Set-CaccaIISSiteAcl requires path to exist } else { $appPoolIdentity = Get-IISAppPool $AppPoolName | Get-IISAppPoolUsername if ($WhatIfPreference -eq $true -and [string]::IsNullOrWhiteSpace($appPoolIdentity)) { $appPoolIdentity = "IIS AppPool\$AppPoolName" } Write-Information "Granting file permissions to '$appPoolIdentity'" $siteAclParams = @{ SitePath = $Path AppPoolIdentity = $appPoolIdentity ModifyPaths = $ModifyPaths ExecutePaths = $ExecutePaths SiteShellOnly = $SiteShellOnly CreateMissingPath = $true } Set-CaccaIISSiteAcl @siteAclParams } Get-IISSite $Name } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Remove-IISAppPool.ps1 #Requires -RunAsAdministrator function Remove-IISAppPool { <# .SYNOPSIS Removes an IIS AppPool and associated file permissions .DESCRIPTION Removes an IIS AppPool and associated file permissions. File permissions on the Temp ASP.Net files will be removed. Where the pool uses ApplicationPoolIdentity, file permissions for this identity will be removed from all physical paths of all Website/Application that is assigned to this pool .PARAMETER Name The name of the pool to remove .PARAMETER InputObject The instance of the pool to remove .PARAMETER Force Delete the pool even if it's assigned to a Site and/or application .PARAMETER Commit Save changes to IIS immediately? Defaults to true .EXAMPLE Remove-CaccaIISAppPool MyAppPool .NOTES Exception thrown when: * Application Pool is assigned to one or more sites/applications and -Force is NOT supplied #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Name')] param ( [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Name', Position = 0)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Object', Position = 0)] [Microsoft.Web.Administration.ApplicationPool] $InputObject, [switch] $Force, [switch] $Commit ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' if (!$PSBoundParameters.ContainsKey('Commit')) { $Commit = $true } $sitesAclPaths = Get-IISSiteAclPath $existingSiteInfo = if ($Force) { @() } else { Get-IISSiteHierarchyInfo } } process { try { Write-Information "Remove app pool '$Name'" [Microsoft.Web.Administration.ServerManager] $manager = Get-IISServerManager $pool = if ($InputObject) { $InputObject } else { $instance = $manager.ApplicationPools[$Name] if (!$instance) { throw "Cannot delete AppPool, '$Name' does not exist" } $instance } $inUse = $existingSiteInfo | Where-Object AppPool_Name -eq $Name if ($inUse) { throw "Cannot delete AppPool, '$Name' is used by one or more Web applications/sites" } $appPoolUsername = Get-IISAppPoolUsername $pool $appPoolIdentityCount = Get-IISAppPool | Get-IISAppPoolUsername | Where-Object { $_ -eq $appPoolUsername } | Measure-Object | Select-Object -Exp Count $isNonSharedIdentity = $appPoolIdentityCount -lt 2 $isAppPoolIdentity = $pool.ProcessModel.IdentityType -eq 'ApplicationPoolIdentity' $allAclPaths = @() if ($isAppPoolIdentity) { $allAclPaths += $sitesAclPaths } if ($isNonSharedIdentity) { $allAclPaths += Get-CaccaTempAspNetFilesPath | ForEach-Object { [PsCustomObject] @{ Path = $_ IdentityReference = $appPoolUsername } } } $allAclPaths | Where-Object IdentityReference -eq $appPoolUsername | Remove-CaccaUserFromAcl if ($Commit) { Start-IISCommitDelay } try { if ($PSCmdlet.ShouldProcess($Name, 'Remove App pool')) { $manager.ApplicationPools.Remove($pool) } if ($Commit) { Stop-IISCommitDelay } } catch { if ($Commit) { Stop-IISCommitDelay -Commit:$false } throw } finally { if ($Commit) { # make sure subsequent scripts will not fail because the ServerManger is now readonly Reset-IISServerManager -Confirm:$false } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Remove-IISSiteBackConnection.ps1 #Requires -RunAsAdministrator function Remove-IISSiteBackConnection { <# .SYNOPSIS Removes the host name(s) from the back connections registry setting .DESCRIPTION Removes the host name from the back connections registry setting .PARAMETER InputObject The backconnection entry to remove .PARAMETER Force Remove even if the host name is assigned to more than one site .EXAMPLE Get-CaccaIISSiteBackConnection MySite | Remove-IISSiteBackConnection .NOTES Exception thrown when: * the host name is assigned to more than one site and -Force is NOT supplied #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [PsCustomObject[]] $InputObject, [switch] $Force ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { Write-Information "Remove backconnection(s) from registry" $shared = $InputObject | Where-Object IsShared if ($shared -and !$Force) { throw "Cannot remove hostname(s) - one or more entries are shared by multiple sites" } $hostName = $InputObject | Select-Object -Exp Hostname -Unique # todo: add -WhatIf support to Remove-TecBoxBackConnectionHostNames if ($PSCmdlet.ShouldProcess($hostName, 'Remove hostname')) { $hostName | Remove-TecBoxBackConnectionHostNames } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Remove-IISSiteHostsFileEntry.ps1 #Requires -RunAsAdministrator function Remove-IISSiteHostsFileEntry { <# .SYNOPSIS Removes the host name(s) from the hosts file .DESCRIPTION Removes the host name(s) from the hosts file .PARAMETER InputObject The host name entry to remove .PARAMETER Force Remove even if the host name is assigned to more than one site .EXAMPLE Get-CaccaIISSiteHostsFileEntry MySite | Remove-CaccaIISSiteHostsFileEntry .NOTES Exception thrown when: * the host name is assigned to more than one site and -Force is NOT supplied #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [PsCustomObject[]] $InputObject, [switch] $Force ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { Write-Information "Remove host name(s) from hosts file" $shared = $InputObject | Where-Object IsShared if ($shared -and !$Force) { throw "Cannot remove hostname(s) - one or more entries are shared by multiple sites" } $hostName = $InputObject | Select-Object -Exp Hostname -Unique # todo: add -WhatIf support to Remove-TecBoxHostnames if ($PSCmdlet.ShouldProcess($hostName, 'Remove hostname')) { $hostName | Remove-TecBoxHostnames } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Remove-IISWebApp.ps1 #Requires -RunAsAdministrator function Remove-IISWebApp { <# .SYNOPSIS Removes an IIS Web Application, it's associated App Pool and file permissions .DESCRIPTION Removes an IIS Web Application, it's associated App Pool and file permissions. By default just the file permissions assigned to the phyical file path of the web application and Temp ASP.Net files will be removed. Use 'ModifyPaths' and/or 'ExecutePaths' to supply additional paths to remove file permissions from. Typically these paths are the ones supplied to the New-CaccaIISWebApp cmdlet. .PARAMETER SiteName The name of the IIS Website to add the application to .PARAMETER Name The logical path name of the application (eg MyApp, /MyApp/NestedApp) .PARAMETER ModifyPaths Additional paths to remove modify file permissions from. Path(s) relative to 'Path' can be supplied .PARAMETER ExecutePaths Additional paths to remove read+excute permissions from. Path(s) relative to 'Path' can be supplied .EXAMPLE Remove-CaccaIISWebApp MySite MyApp .NOTES An App Pool that is also assigned to other Web Application's will NOT be removed #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $SiteName, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ModifyPaths, [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ExecutePaths ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { Write-Information "Remove Web application '$Name'" $SiteName = $SiteName.Trim() $Name = $Name.Trim() if (!$Name.StartsWith('/')) { $Name = '/' + $Name } if ($null -eq $ModifyPaths) { $ModifyPaths = @() } if ($null -eq $ExecutePaths) { $ExecutePaths = @() } # note: NOT throwing to be consistent with IISAdministration\Remove-IISSite $site = Get-IISSite $SiteName if (!$site) { return } # note: NOT throwing to be consistent with IISAdministration\Remove-IISSite $app = $site.Applications[$Name] if (!$app) { Write-Warning "Web Application '$SiteName$Name' does not exist" return } $appPoolIdentity = Get-IISAppPool ($app.ApplicationPoolName) | Get-IISAppPoolUsername $aclInfo = @{ AppPath = $app.VirtualDirectories['/'].PhysicalPath AppPoolIdentity = $appPoolIdentity ModifyPaths = $ModifyPaths ExecutePaths = $ExecutePaths SkipMissingPaths = $true # file permissions for Temp AP.Net Files folders *might* be shared so must skip removing these # cleaning up of orphaned file permissions will happen below when 'Remove-IISAppPool' is run SkipTempAspNetFiles = $true } Remove-CaccaIISSiteAcl @aclInfo Start-IISCommitDelay try { if ($PSCmdlet.ShouldProcess("$SiteName$Name", 'Remove Web Application')) { $site.Applications.Remove($app) } if ($WhatIfPreference -ne $true) { # note: skipping errors when deleting app pool when that pool is shared by other sites/apps Remove-IISAppPool ($app.ApplicationPoolName) -EA Ignore -Commit:$false } Stop-IISCommitDelay } catch { Stop-IISCommitDelay -Commit:$false throw } finally { Reset-IISServerManager -Confirm:$false } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Remove-IISWebsite.ps1 #Requires -RunAsAdministrator function Remove-IISWebsite { <# .SYNOPSIS Removes an IIS Website, it's associated App Pool, file permissions, hosts file and back connection entries .DESCRIPTION Removes an IIS Website, it's associated App Pool, file permissions, hosts file and back connection entries When removing file permissions assigned to the identity of the application pool(s) for the site, the following paths will be searched: - the phyical file path of the site and all their subfolders and files - the phyical file path of all child applications and all their subfolders and files - all Temp ASP.Net files folders .PARAMETER Name The name of the IIS Website to add the application to .PARAMETER KeepBackConnection Don't remove the host name(s) for this site from the back connections list? .PARAMETER KeepHostsFileEntry Don't remove the host name(s) for this site from the hosts file? .EXAMPLE Remove-CaccaIISWebsite MySite .NOTES An App Pool that is also assigned to other Websites will NOT be removed #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)] [string] $Name, [switch] $KeepBackConnection, [switch] $KeepHostsFileEntry ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' } process { try { Write-Information "Remove Website '$Name'" # reload the ServerManager instance inside of the IISAdministration module so as to avoid # concurrency failures where changes have been made via IIS Manager Reset-IISServerManager -Confirm:$false $Name = $Name.Trim() # note: this will produce a warning if site does not exist (this is the desire behaviour - no need to reproduce here) $siteInfo = Get-IISSiteHierarchyInfo $Name if (!$siteInfo) { return } Get-IISSiteAclPath $Name -Recurse | Where-Object IsShared -eq $false | Remove-CaccaUserFromAcl if (!$KeepHostsFileEntry) { Get-IISSiteHostsFileEntry $Name | Where-Object IsShared -eq $false | Remove-IISSiteHostsFileEntry } if (!$KeepBackConnection) { Get-IISSiteBackConnection $Name | Where-Object IsShared -eq $false | Remove-IISSiteBackConnection } Start-IISCommitDelay try { Remove-IISSite $Name -Confirm:$false if ($WhatIfPreference -ne $true) { # note: skipping errors when deleting app pool when that pool is shared by other sites $siteInfo | Select-Object -Exp AppPool_Name -Unique | Remove-IISAppPool -EA Ignore -Commit:$false } Stop-IISCommitDelay } catch { Stop-IISCommitDelay -Commit:$false throw } finally { Reset-IISServerManager -Confirm:$false } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Restart-IISSiteAppPool.ps1 function Restart-IISSiteAppPool { <# .SYNOPSIS Recycles the App pool(s) associated with every application of a Website .DESCRIPTION Recycles the App pool(s) associated with every application of a Website .PARAMETER Name Name of an IIS Website .PARAMETER Force Restart the pool even if it's assigned to more than one Site .EXAMPLE Restart-CaccaIISSiteAppPool Series5 .NOTES Exception thrown when: * Application Pool is assigned to multiple sites and -Force is NOT supplied #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory, ValueFromPipeline)] [string] $Name, [switch] $Force ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' $siteInfos = Get-IISSiteHierarchyInfo } process { try { $siteInfo = $siteInfos | Where-Object Site_Name -eq $Name if ($null -eq $siteInfo) { throw "Cannot recycle app pool(s) for site '$Name'; site does not exist" } $appPoolNames = $siteInfo | Where-Object AppPool_Name | Select-Object -Exp AppPool_Name -Unique if ($null -eq $appPoolNames) { Write-Warning "Cannot recycle app pool(s) for site '$Name'; site is not associated with any app pool" } $otherSiteNames = $siteInfos | Where-Object { $_.Site_Name -ne $Name -and $_.AppPool_Name -in $appPoolNames } | Select-Object -Exp Site_Name -Unique if ($otherSiteNames -and !$Force) { throw "Cannot recycle app pool(s) for site '$Name'; pool assigned to other sites ('$otherSiteNames') and -Force was not supplied" } $appPoolNames | ForEach-Object { $poolName = $_ Write-Information "Recycle app pool '$poolName' for site '$Name'" if ($PSCmdlet.ShouldProcess($poolName, 'Recycle App pool')) { Restart-AppPool $poolName } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } # .\IISSiteInstall\public\Set-IISAppPoolUser.ps1 function Set-IISAppPoolUser { <# .SYNOPSIS Set the Windows account identity of the App Pool .DESCRIPTION Set the Windows account identity of the App Pool .PARAMETER Credential The credential of a specific account to assign as the identity .PARAMETER IdentityType The built-in windows account to assign as the identity .PARAMETER InputObject The App Pool whose identity is to be assigned .PARAMETER Commit Save changes to IIS immediately? Defaults to true .EXAMPLE Get-IISAppPool MyAppPool | Set-CaccaIISAppPoolUser -IdentityType ApplicationPoolIdentity Description ----------- Set the identity of the app ppol to use ApplicationPoolIdentity. In this example, the virtual user 'IIS AppPool\MyAppPool' will be assigned as the Windows identity .EXAMPLE $pswd = ConvertTo-SecureString '(mypassword)' -AsPlainText -Force $creds = [PsCredential]::new("$($env:COMPUTERNAME)\MyLocalUser", $pswd) New-CaccaIISAppPool $tempAppPool -Config { $_ | Set-CaccaIISAppPoolUser $creds -Commit:$false } Description ----------- Create an pool with an identity assigned to a specific user account #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="Dummy credentials")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param ( [PsCredential] $Credential, [ValidateSet('ApplicationPoolIdentity', 'LocalService', 'LocalSystem', 'NetworkService', 'SpecificUser')] [string] $IdentityType = 'SpecificUser', [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [Microsoft.Web.Administration.ApplicationPool] $InputObject, [switch] $Commit ) begin { Set-StrictMode -Version 'Latest' Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $callerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' if ($IdentityType -eq 'SpecificUser' -and !$Credential) { throw "A Credential for 'SpecificUser' must be supplied" } if (!$PSBoundParameters.ContainsKey('Commit')) { $Commit = $true } } process { try { Write-Information "Assign identity of app pool '$($InputObject.Name)'" if ($IdentityType -ne 'SpecificUser') { $dummyPassword = ConvertTo-SecureString 'dummy' -AsPlainText -Force $username = ConvertTo-BuiltInUsername $IdentityType ($InputObject.Name) $Credential = [PsCredential]::new($username, $dummyPassword) } if ($Commit) { Start-IISCommitDelay } try { if ($Credential.UserName -like 'IIS AppPool\*'){ $InputObject.ProcessModel.IdentityType = 'ApplicationPoolIdentity' } elseif($Credential.UserName -eq 'NT AUTHORITY\NETWORK SERVICE') { $InputObject.ProcessModel.IdentityType = 'NetworkService' } elseif ($Credential.UserName -eq 'NT AUTHORITY\SYSTEM') { $InputObject.ProcessModel.IdentityType = 'LocalSystem' } elseif ($Credential.UserName -eq 'NT AUTHORITY\LOCAL SERVICE') { $InputObject.ProcessModel.IdentityType = 'LocalService' } else { $InputObject.ProcessModel.UserName = $Credential.UserName $InputObject.ProcessModel.Password = $Credential.GetNetworkCredential().Password $InputObject.ProcessModel.IdentityType = 'SpecificUser' } if ($Commit) { Stop-IISCommitDelay } } catch { if ($Commit) { Stop-IISCommitDelay -Commit:$false } throw } finally { if ($Commit) { # make sure subsequent scripts will not fail because the ServerManger is now readonly Reset-IISServerManager -Confirm:$false } } } catch { Write-Error -ErrorRecord $_ -EA $callerEA } } } Write-Verbose 'Importing from [C:\MyProjects\IISSiteInstall\IISSiteInstall\classes]' |