Skip to content

Instantly share code, notes, and snippets.

@gtechsltn
Created October 27, 2025 01:48
Show Gist options
  • Save gtechsltn/7f808229e0b0250fbbcb772534338cb3 to your computer and use it in GitHub Desktop.
Save gtechsltn/7f808229e0b0250fbbcb772534338cb3 to your computer and use it in GitHub Desktop.
Create-IIS-Site-App-Advanced-SSL.ps1
<#
.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