functions/enable-d365iispreload.ps1


<#
    .SYNOPSIS
        Enables IIS Preload for the AOSService application pool and website.
         
    .DESCRIPTION
        Configures IIS to preload the AOSService application, improving startup time after X++ compile.
        - Sets Application Pool Start Mode to AlwaysRunning
        - Sets Idle Time-out to 0
        - Enables Preload on the AOSService website
        - Sets doAppInitAfterRestart to true (if Application Initialization is installed)
        - Optionally sets the initializationPage to a custom base URL
         
    .PARAMETER BaseUrl
        The base URL to use for the initializationPage setting in IIS Application Initialization.
        If not provided, the function will attempt to determine the base URL automatically using Get-D365Url.
        Example: https://usnconeboxax1aos.cloud.onebox.dynamics.com
         
    .EXAMPLE
        PS C:\> Enable-D365IISPreload
         
        This will enable IIS Preload and set the initializationPage using the automatically detected base URL.
         
    .EXAMPLE
        PS C:\> Enable-D365IISPreload -BaseUrl "https://usnconeboxax1aos.cloud.onebox.dynamics.com"
         
        This will enable IIS Preload and set the initializationPage to https://usnconeboxax1aos.cloud.onebox.dynamics.com/?mi=DefaultDashboard
         
    .NOTES
        Author: Florian Hopfner (FH-Inway)
        Based on Denis Trunin's article "Enable IIS Preload to Speed Up Restart After X++ Compile" (https://www.linkedin.com/pulse/enable-iis-preload-speed-up-restart-after-x-compile-denis-trunin-86j5c)
        Written with GitHub Copilot GPT-4.1, mostly in agent mode. See commits for prompts.
         
    .LINK
        Get-D365IISPreload
         
    .LINK
        Disable-D365IISPreload
#>

function Enable-D365IISPreload {
    [CmdletBinding()]
    param (
        [string]$BaseUrl = ""
    )

    
    if (-not (Get-Module -ListAvailable -Name WebAdministration)) {
        Write-PSFMessage -Level Warning -Message "The 'WebAdministration' module is not installed. Please install it with: Install-WindowsFeature -Name Web-WebServer -IncludeManagementTools or Install-Module -Name WebAdministration -Scope CurrentUser"
        return
    }
    
    Import-Module WebAdministration -ErrorAction Stop
    
    # Backup IIS Preload configuration before making changes
    $backupDir = Join-Path $Script:DefaultTempPath "IISConfigBackup"
    if (-not (Test-Path $backupDir)) {
        New-Item -Path $backupDir -ItemType Directory | Out-Null
    }
    $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
    $iisConfigBackupFile = Join-Path $backupDir ("IISPreloadConfig.$timestamp.json")
    $preloadConfig = Get-D365IISPreload | ConvertTo-Json -Depth 5
    $preloadConfig | Out-File -FilePath $iisConfigBackupFile -Encoding UTF8
    Write-PSFMessage -Level Host -Message "IIS Preload configuration backed up to $iisConfigBackupFile"
    
    # Ensure IIS Application Initialization feature is installed
    $iisAppInitFeature = Get-WindowsFeature -Name Web-AppInit -ErrorAction SilentlyContinue
    if (-not ($iisAppInitFeature -and $iisAppInitFeature.Installed)) {
        Write-PSFMessage -Level Host -Message "IIS Application Initialization (Web-AppInit) feature is not installed. Installing..."
        Install-WindowsFeature -Name Web-AppInit -IncludeAllSubFeature -IncludeManagementTools -ErrorAction Stop | Out-Null
        Write-PSFMessage -Level Host -Message "IIS Application Initialization feature installed."
    }

    $appPool = "AOSService"
    $site = "AOSService"

    # Set Application Pool to AlwaysRunning and Idle Time-out to 0
    $setAppPoolStartModeParams = @{
        Path  = "IIS:\AppPools\$appPool"
        Name  = 'startMode'
        Value = 'AlwaysRunning'
    }
    Write-PSFMessage -Level Verbose -Message "Setting Application Pool '$appPool' startMode to 'AlwaysRunning'"
    Set-ItemProperty @setAppPoolStartModeParams
    $setAppPoolIdleTimeoutParams = @{
        Path  = "IIS:\AppPools\$appPool"
        Name  = 'processModel.idleTimeout'
        Value = ([TimeSpan]::Zero)
    }
    Write-PSFMessage -Level Verbose -Message "Setting Application Pool '$appPool' idleTimeout to '0'"
    Set-ItemProperty @setAppPoolIdleTimeoutParams

    # Enable Preload on the website
    $setSitePreloadParams = @{
        Path  = "IIS:\Sites\$site"
        Name  = 'applicationDefaults.preloadEnabled'
        Value = $true
    }
    Write-PSFMessage -Level Verbose -Message "Setting Site '$site' applicationDefaults.preloadEnabled to 'True'"
    Set-ItemProperty @setSitePreloadParams

    if (-not $BaseUrl) {
        try {
            $baseUrlObj = Get-D365Url
            if ($baseUrlObj -and $baseUrlObj.Url) {
                $BaseUrl = $baseUrlObj.Url.TrimEnd('/')
            }
        } catch {
            Write-PSFMessage -Level Verbose -Message "Could not determine base URL using Get-D365Url. Defaulting to root."
            $BaseUrl = ""
        }
    }

    # Set the initializationPage for application initialization
    try {
        $initPage = if ($BaseUrl) { "$BaseUrl/?mi=DefaultDashboard" } else { "/?mi=DefaultDashboard" }
        Write-PSFMessage -Level Verbose -Message "Setting Site '$site' initializationPage to '$initPage'"
        $addInitPageParams = @{
            pspath      = "MACHINE/WEBROOT/APPHOST/$site"
            filter      = 'system.webServer/applicationInitialization'
            name        = '.'
            value       = @{ initializationPage = $initPage }
            ErrorAction = 'Stop'
        }
        Add-WebConfigurationProperty @addInitPageParams
    } catch {
        Write-PSFMessage -Level Verbose -Message "Preload page $initPage cannot be set. Application Initialization may not be installed or not available. Skipping initializationPage."
    }

    # Try to set doAppInitAfterRestart if Application Initialization is installed
    try {
        $setDoAppInitParams = @{
            pspath      = "MACHINE/WEBROOT/APPHOST/$site"
            filter      = 'system.webServer/applicationInitialization'
            name        = 'doAppInitAfterRestart'
            value       = 'True'
            ErrorAction = 'Stop'
        }
        Write-PSFMessage -Level Verbose -Message "Setting Site '$site' doAppInitAfterRestart to 'True'"
        Set-WebConfigurationProperty @setDoAppInitParams
    } catch {
        Write-PSFMessage -Level Verbose -Message "doAppInitAfterRestart cannot be set. Application Initialization may not be installed or not available. Skipping doAppInitAfterRestart."
    }

    Write-PSFMessage -Level Host -Message "IIS Preload enabled for $site."

    # Restart IIS service to apply changes
    try {
        Write-PSFMessage -Level Host -Message "Restarting IIS service (W3SVC) to apply preload settings..."
        Restart-Service -Name 'W3SVC' -Force -ErrorAction Stop
        Write-PSFMessage -Level Host -Message "IIS service restarted successfully."
    } catch {
        Write-PSFMessage -Level Warning -Message "Failed to restart IIS service (W3SVC): $_"
    }
}