# .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 = @"
Website: $SiteName
Server: $serverName
Created: $now
HTTPS: https://${Domain}:${HttpsPort}/$AppName
"@ $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 }