private/Install-SoftwareMicrosoftDeploymentToolkit.ps1

function Install-SoftwareMicrosoftDeploymentToolkit {
    <#
    .SYNOPSIS
        Downloads and installs Microsoft Deployment Toolkit from module metadata
 
    .DESCRIPTION
        Reads the MDT version, MSI URL, SHA256 hash, and retirement URL from OSDeploy module
        metadata. The function detects an installed MDT version, downloads or reuses the x64
        MSI, validates its SHA256 hash, and installs it silently with msiexec when MDT is not
        already installed.
 
        DownloadOnly verifies and caches the installer without installing it. WhatIf and
        Confirm apply to the combined download, verification, and installation operation,
        although the version-specific cache directory is created before ShouldProcess.
 
    .PARAMETER DownloadOnly
        Downloads and verifies the MDT MSI without installing it.
 
    .EXAMPLE
        PS> Install-SoftwareMicrosoftDeploymentToolkit
 
        Downloads and verifies the configured MDT MSI and installs it when MDT is absent.
 
    .EXAMPLE
        PS> Install-SoftwareMicrosoftDeploymentToolkit -DownloadOnly
 
        Downloads and verifies the configured MDT MSI without installing it.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        System.Management.Automation.PSCustomObject. Returns the detected or installed version,
        operation status, installer path and URL, and expected SHA256 hash.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Version: 1.0.0
        Date: 2026-08-28
 
        Requires Windows, Administrator rights, curl.exe, msiexec.exe, and complete
        $global:OSDeployModule.Software.mdt metadata. The function displays Microsoft's MDT
        retirement notice on every invocation.
 
        Change Summary:
            - Added metadata-driven download, checksum validation, and retirement guidance.
 
    .LINK
        https://learn.microsoft.com/en-us/intune/configmgr/mdt/
 
    .LINK
        https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/mdt/mdt-retirement
 
    .LINK
        https://web.archive.org/web/20250616094712id_/https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([pscustomobject])]
    param (
        [switch] $DownloadOnly
    )

    if (-not $IsWindows) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Install-SoftwareMicrosoftDeploymentToolkit is supported only on Windows."
    }

    if (-not (Test-IsAdministrator)) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Install-SoftwareMicrosoftDeploymentToolkit requires Administrator rights. Re-run PowerShell as Administrator and try again."
    }

    $curl = Get-Command -Name 'curl.exe' -ErrorAction SilentlyContinue
    if (-not $curl) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] curl.exe is required but was not found. Ensure curl.exe is available in PATH (included with Windows 10 1803+)."
    }

    $msiExec = Get-Command -Name 'msiexec.exe' -ErrorAction SilentlyContinue
    if (-not $msiExec) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] msiexec.exe is required but was not found."
    }

    if (-not $global:OSDeployModule -or -not $global:OSDeployModule.Software.mdt) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] OSDeployCore module metadata is missing required mdt configuration."
    }

    $mdtConfig = $global:OSDeployModule.Software.mdt
    $retirementUrl = [string]$mdtConfig.retirement
    $mdtUrl = [string]$mdtConfig.msi
    $expectedSha256 = ([string]$mdtConfig.sha256).ToLowerInvariant()
    $expectedVersion = [string]$mdtConfig.version

    Write-Warning "[$(Get-Date -Format s)] Microsoft Deployment Toolkit (MDT) has an immediate retirement notice from Microsoft."
    Write-Warning "[$(Get-Date -Format s)] $retirementUrl"

    if ([string]::IsNullOrWhiteSpace($mdtUrl) -or [string]::IsNullOrWhiteSpace($expectedSha256) -or [string]::IsNullOrWhiteSpace($expectedVersion)) {
        throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] OSDeployCore module metadata mdt is incomplete. Required keys: msi, sha256, version."
    }

    $uninstallPaths = @(
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )

    $installedMdt = $null
    foreach ($uninstallPath in $uninstallPaths) {
        $installedMdt = Get-ItemProperty -Path $uninstallPath -ErrorAction SilentlyContinue |
            Where-Object { $_.PSObject.Properties['DisplayName'] -and $_.DisplayName -like 'Microsoft Deployment Toolkit*' } |
            Select-Object -First 1

        if ($installedMdt) {
            break
        }
    }

    if ($installedMdt) {
        $installedVersion = [string]$installedMdt.DisplayVersion
        if ($installedVersion -eq $expectedVersion) {
            Write-Host "[$(Get-Date -Format s)] Microsoft Deployment Toolkit is already installed: $installedVersion (expected: $expectedVersion)" -ForegroundColor Green
        }
        else {
            Write-Warning "[$(Get-Date -Format s)] Microsoft Deployment Toolkit is installed but version mismatch. Installed: $installedVersion, Expected: $expectedVersion"
        }
    }

    $downloadDir = Join-Path -Path $Script:OSDeployCoreSoftwarePath -ChildPath "Microsoft.DeploymentToolkit_$expectedVersion"
    New-Item -Path $downloadDir -ItemType Directory -Force | Out-Null
    $installerPath = Join-Path -Path $downloadDir -ChildPath 'MicrosoftDeploymentToolkit_x64.msi'
    $skippedInstall = $false

    if ($PSCmdlet.ShouldProcess('Microsoft Deployment Toolkit', 'Download, verify SHA256, and install silently')) {
        $actualSha256 = if (Test-Path -Path $installerPath -PathType Leaf) {
            (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant()
        }

        if ($actualSha256 -eq $expectedSha256) {
            Write-Host "[$(Get-Date -Format s)] Using cached Microsoft Deployment Toolkit MSI." -ForegroundColor DarkGray
        }
        else {
            Remove-Item -Path $installerPath -Force -ErrorAction SilentlyContinue
            Write-Host "[$(Get-Date -Format s)] Downloading Microsoft Deployment Toolkit MSI..." -ForegroundColor DarkGray
            & $curl.Source --insecure --location --retry 5 --output $installerPath --url $mdtUrl
            if ($LASTEXITCODE -ne 0) {
                throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Failed to download MDT MSI (curl.exe exit code $LASTEXITCODE)."
            }

            $actualSha256 = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant()
        }

        if ($actualSha256 -ne $expectedSha256) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] MDT MSI checksum mismatch. Expected $expectedSha256 but got $actualSha256."
        }

        if ($DownloadOnly) {
            Write-Host "[$(Get-Date -Format s)] DownloadOnly: skipping installation." -ForegroundColor DarkGray
            return [pscustomobject]@{
                ProductName    = 'Microsoft Deployment Toolkit'
                Version        = $expectedVersion
                WasInstalled   = $false
                SkippedInstall = $false
                InstallerPath  = $installerPath
                InstallerUrl   = $mdtUrl
                Sha256         = $actualSha256
                DownloadOnly   = $true
            }
        }

        if ($installedMdt) {
            # MDT already installed; skip re-install
            return [pscustomobject]@{
                ProductName    = $installedMdt.DisplayName
                Version        = $installedVersion
                WasInstalled   = $false
                SkippedInstall = $true
                InstallerPath  = $installerPath
                InstallerUrl   = $mdtUrl
                Sha256         = $actualSha256
            }
        }

        Write-Host "[$(Get-Date -Format s)] Installing Microsoft Deployment Toolkit..." -ForegroundColor DarkGray
        $msiArgs = @('/i', $installerPath, '/qn', '/norestart')
        $process = Start-Process -FilePath $msiExec.Source -ArgumentList $msiArgs -Wait -PassThru
        if ($process.ExitCode -ne 0) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] MDT installation failed with exit code $($process.ExitCode)."
        }

        Update-OSDeploySessionEnvironment

        $installedMdt = $null
        foreach ($uninstallPath in $uninstallPaths) {
            $installedMdt = Get-ItemProperty -Path $uninstallPath -ErrorAction SilentlyContinue |
                Where-Object { $_.PSObject.Properties['DisplayName'] -and $_.DisplayName -like 'Microsoft Deployment Toolkit*' } |
                Select-Object -First 1

            if ($installedMdt) {
                break
            }
        }

        if (-not $installedMdt) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] MDT install completed but product was not found in uninstall registry."
        }

        $installedVersion = [string]$installedMdt.DisplayVersion
        if ($installedVersion -ne $expectedVersion) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] MDT installation completed but version mismatch. Installed: $installedVersion, Expected: $expectedVersion."
        }

        Write-Host "[$(Get-Date -Format s)] Microsoft Deployment Toolkit installed successfully: $installedVersion" -ForegroundColor Green

        return [pscustomobject]@{
            ProductName    = $installedMdt.DisplayName
            Version        = $installedVersion
            WasInstalled   = $true
            SkippedInstall = $false
            InstallerPath  = $installerPath
            InstallerUrl   = $mdtUrl
            Sha256         = $actualSha256
        }
    }

    $skippedInstall = $true
    [pscustomobject]@{
        ProductName    = 'Microsoft Deployment Toolkit'
        Version        = $null
        WasInstalled   = $false
        SkippedInstall = $skippedInstall
        InstallerPath  = $installerPath
        InstallerUrl   = $mdtUrl
        Sha256         = $expectedSha256
    }
}