Functions/Initialize-InstallerCacheFileShare.ps1
<#
.SYNOPSIS Prepares the FixMissingMSI automation environment: downloads the FixMissingMSI tool, creates the shared folder structure, stages tool binaries, and grants access to Domain Computers. Supports -WhatIf and -Confirm switches. .DESCRIPTION Sets up the fileshare folders needed to automate FixMissingMSI. It performs the following: 1. Downloads the FixMissingMSI zip from GitHub release. 2. Expands the archive to a temp working folder. 3. Creates a standardized layout beneath $FileSharePath\FixMissingMSI: Cache\Products\ Cache\Patches\ Reports\ 4. Copies the FixMissingMSI binaries into $FileSharePath\FixMissingMSI. 5. Grants NTFS permissions: - App folder (FixMissingMSI): "Domain Computers" = Read & Execute - Cache and Reports: "Domain Computers" = Read, Write (CI/OI) > Note: FixMissingMSI is a GUI application without a native CLI. Later steps invoke its > internal methods via .NET reflection to run it non-interactively. This script > only stages the tool and prepares directories and permissions. .PARAMETER FileSharePath UNC path for the share root. Example: \\FS01\ .PARAMETER FixMissingMsiUri URI to the FixMissingMSI zip in the upstream repository. Defaults to the current latest: V2.2.1 .PARAMETER TempPath Local working directory for download and extraction. Defaults to $env:TEMP. .EXAMPLE PS> Initialize-InstallerCacheFileShare -FileSharePath "\\FS01\Software" Creates \\FS01\Software\FixMissingMSI with the required subfolders, downloads and stages FixMissingMSI, sets read/execute on the app folder and read/write on Cache and Reports for Domain Computers. .NOTES Author: Joey Eckelbarger Credits: FixMissingMSI is authored and maintained by suyouquan Source: https://github.com/suyouquan/SQLSetupTools/releases/tag/V2.2.1 Security: App folder is readable but not writable by "Domain Computers". Cache and Reports are writable to allow servers to upload MSI/MSP files and CSV reports. Requires: - PowerShell 5.1+ (for Expand-Archive) - Network access to the target file server - NTFS modify rights on the target path #> function Initialize-InstallerCacheFileShare { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory = $true)] [string]$FileSharePath, [uri]$FixMissingMsiUri = 'https://github.com/suyouquan/SQLSetupTools/releases/download/V2.2.1/FixMissingMSI_V2.2.1_For_NET45.zip', [string]$TempPath = $env:TEMP ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Progress UI slows iwr download speed noticeably. # Normalize and compose paths once for clarity and to avoid typos. $ShareRoot = $FileSharePath.TrimEnd('\') $AppFolder = Join-Path $ShareRoot 'FixMissingMSI' $CacheRoot = Join-Path $AppFolder 'Cache' $CacheProductsPath = Join-Path $CacheRoot 'Products' $CachePatchesPath = Join-Path $CacheRoot 'Patches' $ReportsPath = Join-Path $AppFolder 'Reports' $ZipPath = Join-Path $TempPath 'FixMissingMSI.zip' $ExpandPath = Join-Path $TempPath 'FixMissingMSI_Expanded' # Ensure TLS 1.2 on older hosts (e.g., Server 2016) to avoid protocol negotiation failures. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # High-level guard: one decision covers the whole provisioning action. if (-not $PSCmdlet.ShouldProcess($ShareRoot, 'Provision FixMissingMSI environment (create folders, download, copy, set ACLs)')) { return } # Clean previous temp artifacts to avoid mixing versions. # Separate guard for destructive actions to make -Confirm meaningful here. if (Test-Path -LiteralPath $ZipPath) { if ($PSCmdlet.ShouldProcess($ZipPath, 'Remove existing zip')) { Remove-Item -LiteralPath $ZipPath -Force } } if (Test-Path -LiteralPath $ExpandPath) { if ($PSCmdlet.ShouldProcess($ExpandPath, 'Remove previous expanded folder')) { Remove-Item -LiteralPath $ExpandPath -Recurse -Force } } # Create folder layout idempotently. Try { foreach ($folder in @($AppFolder,$CacheProductsPath,$CachePatchesPath,$ReportsPath)) { if (-not (Test-Path -LiteralPath $folder)) { New-Item -ItemType Directory -Path $folder -Force | Out-Null } } } Catch { Write-Error $_ Throw "Failed to create folders on $FileSharePath" } # Download upstream tool. Invoke-WebRequest -Uri $FixMissingMsiUri -UseBasicParsing -OutFile $ZipPath # Unblock and expand. MOTW can block execution in some environments. I don't think iwr downloads get tagged with MOTW but just to be 100% I included it. Unblock-File -LiteralPath $ZipPath Expand-Archive -LiteralPath $ZipPath -DestinationPath $ExpandPath -Force # Copy tool files into $ExpandPath Copy-Item -Path (Join-Path $ExpandPath '*') -Destination $AppFolder -Recurse -Force # Identity and rights # targeted servers need to copy up msi/msp and write .csv reports. $domainComputers = 'Domain Computers' $readExec = [System.Security.AccessControl.FileSystemRights]'ReadAndExecute' $readWriteDelete = [System.Security.AccessControl.FileSystemRights]::Modify $ci = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit' $oi = [System.Security.AccessControl.InheritanceFlags]'ObjectInherit' $inheritBoth = $ci -bor $oi $none = [System.Security.AccessControl.PropagationFlags]::None $allow = [System.Security.AccessControl.AccessControlType]::Allow # 1) App folder: ensure Domain Computers have Read & Execute only. # Break inheritance on the app folder so any parent write permissions do not flow down. $aclApp = Get-Acl -LiteralPath $AppFolder $aclApp.SetAccessRuleProtection($true, $true) # protect; convert inherited to explicit $aceReadExec = New-Object System.Security.AccessControl.FileSystemAccessRule($domainComputers, $readExec, $inheritBoth, $none, $allow) [void]$aclApp.AddAccessRule($aceReadExec) Set-Acl -Path $AppFolder -AclObject $aclApp # 2) Cache: Domain Computers Read + Write (CI/OI) $aclCache = Get-Acl -LiteralPath $CacheRoot $aceRW = New-Object System.Security.AccessControl.FileSystemAccessRule($domainComputers, $readWriteDelete, $inheritBoth, $none, $allow) [void]$aclCache.AddAccessRule($aceRW) Set-Acl -Path $CacheRoot -AclObject $aclCache # 3) Reports: Domain Computers Read + Write (CI/OI) $aclReports = Get-Acl -LiteralPath $ReportsPath [void]$aclReports.AddAccessRule($aceRW) Set-Acl -Path $ReportsPath -AclObject $aclReports Write-Output "Environment setup complete." [PSCustomObject]@{ "Share Root" = $ShareRoot "FixMissingMSI App Path" = $AppFolder "Reports Path" = $ReportsPath "Cache Paths" = @($CacheProductsPath,$CachePatchesPath) -join "`n" } | Format-List } |