Created
October 27, 2025 01:48
-
-
Save gtechsltn/7f808229e0b0250fbbcb772534338cb3 to your computer and use it in GitHub Desktop.
Create-IIS-Site-App-Advanced-SSL.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Advanced: Create IIS Website + Application + App Pool + SSL binding (IIS Express style cert for localhost). | |
| .DESCRIPTION | |
| - Creates AppPool, Website, Application and sample index.html. | |
| - Checks ports (HTTP and HTTPS) and warns/prompts. | |
| - Uses an existing LocalMachine\My cert with CN=localhost or FriendlyName containing "IIS Express". | |
| - If not found, creates a self-signed certificate (New-SelfSignedCertificate) with FriendlyName "IIS Express Development Certificate". | |
| - Adds HTTPS host-header binding (hostname = localhost) on port 44384 and assigns certificate. | |
| - Logging, colored output, Try/Catch, recap. | |
| .NOTES | |
| Run PowerShell as Administrator. | |
| Example: | |
| .\Create-IIS-Site-App-Advanced-SSL.ps1 -SiteName "MyAspNetSite" -Port 8084 -AppName "MyApp" -LogPath "C:\temp\iis-ssl.log" | |
| #> | |
| [CmdletBinding(DefaultParameterSetName='Normal')] | |
| param ( | |
| [Parameter(Mandatory = $false)] | |
| [string] $SiteName = "MyAspNetSite", | |
| [Parameter(Mandatory = $false)] | |
| [int] $Port = 8084, # HTTP port for site | |
| [Parameter(Mandatory = $false)] | |
| [string] $AppPoolName = "MyAspNetSitePool", | |
| [Parameter(Mandatory = $false)] | |
| [string] $RootPath = "C:\inetpub\wwwroot\WebSites\MyAspNetSite", | |
| [Parameter(Mandatory = $false)] | |
| [string] $AppName = "MyApp", | |
| [Parameter(Mandatory = $false)] | |
| [ValidateSet("No Managed Code","v2.0","v4.0")] | |
| [string] $RuntimeVersion = "v4.0", | |
| [Parameter(Mandatory = $false)] | |
| [ValidateSet("Integrated","Classic")] | |
| [string] $PipelineMode = "Integrated", | |
| [Parameter(Mandatory = $false)] | |
| [string] $LogPath = "", | |
| [Parameter(Mandatory = $false)] | |
| [switch] $Silent | |
| ) | |
| # SSL-specific defaults (per your choice) | |
| $Domain = "localhost" # Domain to bind (hostname) | |
| $HttpsPort = 44384 # HTTPS port | |
| $CertFriendlyName = "IIS Express Development Certificate" # Friendly name to search/create | |
| function Write-Log { | |
| param([string] $Message, [string] $Level = "INFO") | |
| $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") | |
| $line = "[$timestamp] [$Level] $Message" | |
| if ($LogPath) { | |
| try { | |
| $dir = Split-Path -Parent $LogPath | |
| if ($dir -and -not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory | Out-Null } | |
| Add-Content -Path $LogPath -Value $line | |
| } catch { | |
| Write-Host "Warning: Failed to write log to $LogPath - $_" -ForegroundColor Yellow | |
| } | |
| } | |
| switch ($Level) { | |
| "INFO" { Write-Host $line -ForegroundColor White } | |
| "SUCCESS" { Write-Host $line -ForegroundColor Green } | |
| "WARN" { Write-Host $line -ForegroundColor Yellow } | |
| "ERROR" { Write-Host $line -ForegroundColor Red } | |
| default { Write-Host $line } | |
| } | |
| } | |
| function Check-Admin { | |
| $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() | |
| $principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity) | |
| return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| } | |
| function Test-PortInUse { | |
| param([int] $p) | |
| try { | |
| if (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue) { | |
| $conn = Get-NetTCPConnection -LocalPort $p -ErrorAction SilentlyContinue | |
| return ($conn -ne $null) | |
| } else { | |
| $ns = netstat -ano | Select-String -Pattern ":\b$p\b" | |
| return ($ns -ne $null) | |
| } | |
| } catch { | |
| Write-Log "Port check failed: $_" "WARN" | |
| return $false | |
| } | |
| } | |
| # Summary | |
| $summary = [ordered]@{ | |
| SiteCreated = $false | |
| AppCreated = $false | |
| AppPoolCreated = $false | |
| IndexCreated = $false | |
| HTTPSBinding = $false | |
| CertCreated = $false | |
| Started = $false | |
| Errors = @() | |
| } | |
| try { | |
| # Pre-flight admin check | |
| if (-not (Check-Admin)) { | |
| Write-Host "ERROR: This script must be run as Administrator." -ForegroundColor Red | |
| Write-Log "Script not run as Administrator. Aborting." "ERROR" | |
| throw "Administrator privileges required." | |
| } | |
| Import-Module WebAdministration -ErrorAction Stop | |
| Write-Log "WebAdministration module imported." | |
| Write-Log "Parameters: SiteName=$SiteName, HTTP Port=$Port, HTTPS Port=$HttpsPort, AppPoolName=$AppPoolName, RootPath=$RootPath, AppName=$AppName, Runtime=$RuntimeVersion, Pipeline=$PipelineMode, Domain=$Domain" | |
| # Check HTTP port | |
| if (Test-PortInUse -p $Port) { | |
| $msg = "HTTP Port $Port appears to be in use." | |
| Write-Log $msg "WARN" | |
| if (-not $Silent) { | |
| $resp = Read-Host "$msg`nProceed anyway? (Y/N)" | |
| if ($resp -notin @('Y','y','Yes','yes')) { throw "User aborted due to HTTP port in use." } | |
| } else { throw $msg } | |
| } else { Write-Log "HTTP Port $Port is available." "INFO" } | |
| # Check HTTPS port | |
| if (Test-PortInUse -p $HttpsPort) { | |
| $msg2 = "HTTPS Port $HttpsPort appears to be in use. Creating an HTTPS binding may fail or require different port." | |
| Write-Log $msg2 "WARN" | |
| if (-not $Silent) { | |
| $resp2 = Read-Host "$msg2`nProceed with HTTPS binding anyway? (Y/N)" | |
| if ($resp2 -notin @('Y','y','Yes','yes')) { Write-Log "Skipping HTTPS binding per user choice." "WARN" } | |
| # continue; we will attempt binding and handle errors | |
| } else { Write-Log "Silent mode: proceeding but HTTPS bind may fail." "WARN" } | |
| } else { Write-Log "HTTPS Port $HttpsPort is available." "INFO" } | |
| # Create directories | |
| if (-not (Test-Path $RootPath)) { | |
| New-Item -Path $RootPath -ItemType Directory -Force | Out-Null | |
| Write-Log "Created site root directory: $RootPath" "SUCCESS" | |
| } else { | |
| Write-Log "Site root directory already exists: $RootPath" "INFO" | |
| } | |
| $appPhysicalPath = Join-Path $RootPath $AppName | |
| if (-not (Test-Path $appPhysicalPath)) { | |
| New-Item -Path $appPhysicalPath -ItemType Directory -Force | Out-Null | |
| Write-Log "Created application directory: $appPhysicalPath" "SUCCESS" | |
| } else { | |
| Write-Log "Application directory already exists: $appPhysicalPath" "INFO" | |
| } | |
| # Create index.html | |
| $indexFile = Join-Path $appPhysicalPath "index.html" | |
| if (-not (Test-Path $indexFile)) { | |
| $serverName = $env:COMPUTERNAME | |
| $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| $content = @" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>Welcome to $AppName</title> | |
| </head> | |
| <body> | |
| <h1>Success! Your application '$AppName' is running.</h1> | |
| <p>Website: $SiteName</p> | |
| <p>Server: $serverName</p> | |
| <p>Created: $now</p> | |
| <p>HTTPS: https://${Domain}:${HttpsPort}/$AppName</p> | |
| </body> | |
| </html> | |
| "@ | |
| $content | Out-File -FilePath $indexFile -Encoding UTF8 -Force | |
| Write-Log "Created index.html at $indexFile" "SUCCESS" | |
| $summary.IndexCreated = $true | |
| } else { | |
| Write-Log "index.html already exists at $indexFile" "INFO" | |
| } | |
| # Create App Pool | |
| $appPoolPath = "IIS:\AppPools\$AppPoolName" | |
| if (-not (Test-Path $appPoolPath)) { | |
| New-WebAppPool -Name $AppPoolName | |
| Write-Log "Created Application Pool: $AppPoolName" "SUCCESS" | |
| $summary.AppPoolCreated = $true | |
| } else { | |
| Write-Log "Application Pool already exists: $AppPoolName" "INFO" | |
| } | |
| # Configure runtime and pipeline | |
| try { | |
| $clr = if ($RuntimeVersion -eq "No Managed Code") { "" } else { $RuntimeVersion } | |
| Set-ItemProperty "IIS:\AppPools\$AppPoolName" -Name managedRuntimeVersion -Value $clr -ErrorAction Stop | |
| Set-ItemProperty "IIS:\AppPools\$AppPoolName" -Name managedPipelineMode -Value $PipelineMode -ErrorAction Stop | |
| Write-Log "Configured AppPool '$AppPoolName' with runtime='$RuntimeVersion' and pipeline='$PipelineMode'." "SUCCESS" | |
| } catch { | |
| $err = "Failed to configure AppPool properties: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| # Create Website | |
| $siteExists = $false | |
| try { | |
| $site = Get-Website -Name $SiteName -ErrorAction SilentlyContinue | |
| if ($site) { $siteExists = $true } | |
| } catch { } | |
| if (-not $siteExists) { | |
| try { | |
| New-Website -Name $SiteName -Port $Port -PhysicalPath $RootPath -ApplicationPool $AppPoolName -ErrorAction Stop | |
| Write-Log "Created Website: $SiteName (Port $Port)" "SUCCESS" | |
| $summary.SiteCreated = $true | |
| } catch { | |
| $err = "Failed to create website: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| } else { | |
| Write-Log "Website already exists: $SiteName" "INFO" | |
| } | |
| # Create Application under site | |
| try { | |
| $app = Get-WebApplication -Site $SiteName -Name $AppName -ErrorAction SilentlyContinue | |
| if (-not $app) { | |
| New-WebApplication -Site $SiteName -Name $AppName -PhysicalPath $appPhysicalPath -ApplicationPool $AppPoolName -ErrorAction Stop | |
| Write-Log "Created Application '$AppName' under site '$SiteName'." "SUCCESS" | |
| $summary.AppCreated = $true | |
| } else { | |
| Write-Log "Application '$AppName' already exists under site '$SiteName'." "INFO" | |
| } | |
| } catch { | |
| $err = "Failed to create application: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| # ---------- SSL: find or create cert ---------- | |
| try { | |
| Write-Log "Searching for existing certificate (LocalMachine\\My) with CN=localhost or FriendlyName like 'IIS Express'." "INFO" | |
| $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { | |
| ($_.Subject -like "CN=$Domain") -or ($_.FriendlyName -like "*IIS Express*") | |
| } | Sort-Object NotAfter -Descending | Select-Object -First 1 | |
| if (-not $cert) { | |
| Write-Log "No existing cert found. Creating self-signed certificate for $Domain with FriendlyName '$CertFriendlyName'." "INFO" | |
| # Create self-signed cert in LocalMachine\My | |
| $cert = New-SelfSignedCertificate -DnsName $Domain -CertStoreLocation Cert:\LocalMachine\My -FriendlyName $CertFriendlyName -NotAfter (Get-Date).AddYears(5) | |
| Write-Log "Created certificate with thumbprint $($cert.Thumbprint)" "SUCCESS" | |
| $summary.CertCreated = $true | |
| } else { | |
| Write-Log "Using existing certificate: Subject='$($cert.Subject)' FriendlyName='$($cert.FriendlyName)' Thumbprint='$($cert.Thumbprint)'" "SUCCESS" | |
| } | |
| } catch { | |
| $err = "Failed to find/create certificate: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| # ---------- Create HTTPS binding (host header) and assign cert ---------- | |
| try { | |
| # If https binding for same host and port exists, skip creation; otherwise create | |
| $existingHttps = Get-WebBinding -Name $SiteName -Protocol "https" -ErrorAction SilentlyContinue | Where-Object { | |
| ($_.bindingInformation -match ":${HttpsPort}:") -and ($_.HostHeader -ieq $Domain -or $_.hostHeader -ieq $Domain) | |
| } | |
| if ($existingHttps) { | |
| Write-Log "HTTPS binding for site '$SiteName' host '$Domain' on port $HttpsPort already exists." "INFO" | |
| $summary.HTTPSBinding = $true | |
| } else { | |
| # Create https binding with host header | |
| New-WebBinding -Name $SiteName -Protocol https -Port $HttpsPort -HostHeader $Domain -IPAddress "*" -ErrorAction Stop | |
| Write-Log "Created HTTPS binding: https://${Domain}:${HttpsPort}" "SUCCESS" | |
| $summary.HTTPSBinding = $true | |
| } | |
| # Assign certificate to the binding - use AddSslCertificate method on binding object | |
| $binding = Get-WebBinding -Name $SiteName -Protocol https | | |
| Where-Object { | |
| $_.bindingInformation -like "*:${HttpsPort}:*" -and | |
| ($_.HostHeader -eq $Domain -or $_.HostHeader -eq "" -or $_.HostHeader -eq $null) | |
| } | |
| if ($binding) { | |
| Write-Log "Assigning SSL certificate with thumbprint $CertThumbprint..." | |
| $binding.AddSslCertificate($CertThumbprint, "My") | |
| Write-Log "SSL certificate assigned successfully." | |
| } else { | |
| throw "HTTPS binding was not found after creation; cannot assign certificate." | |
| } | |
| } catch { | |
| $err = "Failed to create HTTPS binding or assign certificate: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| # Start AppPool and Website | |
| try { | |
| Start-WebAppPool -Name $AppPoolName -ErrorAction Stop | |
| Write-Log "Started AppPool: $AppPoolName" "SUCCESS" | |
| } catch { | |
| $err = "Failed to start AppPool: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| try { | |
| Start-Website -Name $SiteName -ErrorAction Stop | |
| Write-Log "Started Website: $SiteName" "SUCCESS" | |
| $summary.Started = $true | |
| } catch { | |
| $err = "Failed to start Website: $_" | |
| Write-Log $err "ERROR" | |
| $summary.Errors += $err | |
| throw $err | |
| } | |
| } catch { | |
| $globalErr = $_.Exception.Message | |
| Write-Log "Script aborted with error: $globalErr" "ERROR" | |
| if ($_.Exception.InnerException) { | |
| Write-Log "InnerException: $($_.Exception.InnerException.Message)" "ERROR" | |
| } | |
| } finally { | |
| # Recap | |
| Write-Host "" | |
| Write-Host "==================== SUMMARY ====================" -ForegroundColor Cyan | |
| Write-Host ("Site: {0}`nApp: {1}`nAppPool: {2}`nRootPath: {3}`nHTTP Port: {4}`nHTTPS Port: {5}" -f $SiteName, $AppName, $AppPoolName, $RootPath, $Port, $HttpsPort) | |
| Write-Host "" | |
| if ($summary.AppPoolCreated) { Write-Host "AppPool created: YES" -ForegroundColor Green } else { Write-Host "AppPool created: NO (maybe existed)" -ForegroundColor Yellow } | |
| if ($summary.SiteCreated) { Write-Host "Website created: YES" -ForegroundColor Green } else { Write-Host "Website created: NO (maybe existed)" -ForegroundColor Yellow } | |
| if ($summary.AppCreated) { Write-Host "Application created: YES" -ForegroundColor Green } else { Write-Host "Application created: NO (maybe existed)" -ForegroundColor Yellow } | |
| if ($summary.IndexCreated) { Write-Host "index.html created: YES" -ForegroundColor Green } else { Write-Host "index.html created: NO (maybe existed)" -ForegroundColor Yellow } | |
| if ($summary.CertCreated) { Write-Host "Certificate created: YES" -ForegroundColor Green } else { Write-Host "Certificate created: NO (used existing)" -ForegroundColor Yellow } | |
| if ($summary.HTTPSBinding) { Write-Host "HTTPS binding: YES" -ForegroundColor Green } else { Write-Host "HTTPS binding: NO" -ForegroundColor Red } | |
| if ($summary.Started) { Write-Host "Started: Website & AppPool started" -ForegroundColor Green } else { Write-Host "Started: NOT fully started" -ForegroundColor Red } | |
| if ($summary.Errors.Count -gt 0) { | |
| Write-Host "`nErrors encountered:" -ForegroundColor Red | |
| foreach ($e in $summary.Errors) { Write-Host $e -ForegroundColor Red } | |
| } else { | |
| Write-Host "`nNo errors reported." -ForegroundColor Green | |
| } | |
| if ($LogPath) { | |
| Write-Host "`nLog file: $LogPath" -ForegroundColor Cyan | |
| } | |
| Write-Host "Access URLs (if started):" -ForegroundColor Cyan | |
| Write-Host (" HTTP : http://localhost:{0}/{1}" -f $Port, $AppName) -ForegroundColor Cyan | |
| Write-Host (" HTTPS : https://{0}:{1}/{2}" -f $Domain, $HttpsPort, $AppName) -ForegroundColor Cyan | |
| Write-Host "=================================================" -ForegroundColor Cyan | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment