Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
$ErrorActionPreference = 'Stop'

# ------------------------------------------------------------------
# Global Safety & TLS
# ------------------------------------------------------------------
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# ------------------------------------------------------------------
# Package Context
# ------------------------------------------------------------------
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$packageName = $env:ChocolateyPackageName
$packageVersion = $env:ChocolateyPackageVersion

$zipName = 'arcgistools3.6.zip'
$zipPath = Join-Path -Path $toolsDir -ChildPath $zipName
$expectedMsiName = 'ArcGISPro.msi'
$logPath = Join-Path -Path $env:TEMP -ChildPath "${packageName}.${packageVersion}.log"

# ------------------------------------------------------------------
# ProGet Asset Download Configuration
# ------------------------------------------------------------------
$zipUrl = 'https://proget/assets/Choco-Applications/content/ArcGIS/ArcGIS%20Pro/arcgistools3.6.zip'
$zipChecksum = '0e114dd159d16f71bb06aa333b3ce898e756cfc12ddb417d21079749952bcb30'
$zipChecksumType = 'sha256'

# API key must be supplied securely by the deployment environment
$proGetAssetApiKey = '#############################'

# ------------------------------------------------------------------
# Validate Required Environment Values
# ------------------------------------------------------------------
if ([string]::IsNullOrWhiteSpace($packageName)) {
throw "Chocolatey package name environment variable is missing."
}

if ([string]::IsNullOrWhiteSpace($packageVersion)) {
throw "Chocolatey package version environment variable is missing."
}

if ([string]::IsNullOrWhiteSpace($proGetAssetApiKey)) {
throw "Missing ProGet asset API key. Set environment variable 'ProGetAssetApiKey' before installation."
}

# ------------------------------------------------------------------
# Clean Previous Package Artifacts
# ------------------------------------------------------------------
Write-Host "Cleaning previous package artifacts from '${toolsDir}'..."

if (Test-Path -Path $zipPath) {
Remove-Item -Path $zipPath -Force -ErrorAction Stop
}

Get-ChildItem -Path $toolsDir -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -ieq '.msi' } |
ForEach-Object {
try {
Remove-Item -Path $_.FullName -Force -ErrorAction Stop
}
catch {
Write-Warning "Unable to remove stale file '${($_.FullName)}'. Continuing."
}
}

# ------------------------------------------------------------------
# Download ZIP from ProGet HTTP Endpoint using X-ApiKey
# ------------------------------------------------------------------
Write-Host "Downloading ArcGIS Pro ZIP from ProGet endpoint using API key authentication..."

$headers = @{
'X-ApiKey' = $proGetAssetApiKey
'User-Agent' = 'chocolatey command line'
}

try {
Invoke-WebRequest `
-Uri $zipUrl `
-Headers $headers `
-OutFile $zipPath `
-UseBasicParsing `
-ErrorAction Stop
}
catch {
throw "Failed to download ArcGIS Pro ZIP from ProGet endpoint. URL: '${zipUrl}'. Error: $($_.Exception.Message)"
}

if (-not (Test-Path -Path $zipPath)) {
throw "ZIP download failed. File not found at '${zipPath}'."
}

$fileInfo = Get-Item -Path $zipPath -ErrorAction Stop
Write-Host "Downloaded file size: $($fileInfo.Length) bytes"

if ($fileInfo.Length -le 0) {
throw "Downloaded ZIP file is empty: '${zipPath}'"
}

# ------------------------------------------------------------------
# Validate Downloaded Content Is Not HTML/Login Page
# ------------------------------------------------------------------
$firstLine = $null
try {
$firstLine = Get-Content -Path $zipPath -TotalCount 1 -ErrorAction Stop
}
catch {
$firstLine = $null
}

if ($firstLine -match '<!DOCTYPE html|<html|<head|<body|<title>Log In</title>') {
throw "Downloaded content is HTML instead of the expected ZIP. Verify the ProGet endpoint URL and API key scope. URL: '${zipUrl}'"
}

# ------------------------------------------------------------------
# Validate ZIP Checksum
# ------------------------------------------------------------------
Get-ChecksumValid -File $zipPath -Checksum $zipChecksum -ChecksumType $zipChecksumType

# ------------------------------------------------------------------
# Extract ZIP
# ------------------------------------------------------------------
Write-Host "Extracting ZIP to '${toolsDir}'..."

Get-ChocolateyUnzip -FileFullPath $zipPath -Destination $toolsDir

# ------------------------------------------------------------------
# Locate MSI Recursively
# ------------------------------------------------------------------
Write-Host "Searching recursively for '${expectedMsiName}'..."

$msiCandidates = Get-ChildItem -Path $toolsDir -Recurse -File -ErrorAction Stop |
Where-Object { $_.Name -ieq $expectedMsiName }

if (-not $msiCandidates) {
throw "Expected MSI '${expectedMsiName}' was not found under '${toolsDir}' after extraction."
}

if ($msiCandidates.Count -gt 1) {
$candidateList = ($msiCandidates | Select-Object -ExpandProperty FullName) -join [Environment]::NewLine
throw "Multiple MSI files named '${expectedMsiName}' were found. Cannot continue safely.`n${candidateList}"
}

$msiPath = $msiCandidates[0].FullName

if (-not (Test-Path -Path $msiPath)) {
throw "Resolved MSI path does not exist: '${msiPath}'"
}

Write-Host "Resolved MSI path: ${msiPath}"

# ------------------------------------------------------------------
# Install MSI
# ------------------------------------------------------------------
Write-Host "Installing ArcGIS Pro..."

$packageArgs = @{
packageName = $packageName
fileType = 'MSI'
file = $msiPath
softwareName = 'arcgispro*'
checksum = '703da43c59dbbdd2bbcc47c4173e6c90a110cc6ad697127c84158ddfd35acf0e'
checksumType = 'sha256'
silentArgs = @(
'/qn'
'/norestart'
'ACCEPTEULA=yes'
'ALLUSERS=1'
'CHECKFORUPDATESATSTARTUP=0'
'AUTHORIZATION_TYPE=NAMED_USER'
'LOCK_AUTH_SETTINGS=TRUE'
'ESRI_LICENSE_HOST=111.11.11.11'
'SOFTWARE_CLASS=Professional'
"/l*v `"$logPath`""
) -join ' '
validExitCodes = @(0, 3010)
useOriginalLocation = $true
}

Install-ChocolateyInstallPackage @packageArgs

Write-Host "Installation completed successfully."
Write-Host "MSI log file: ${logPath}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

# -------------------------------------------------------------------------------------------------
# Chocolatey Core Apps Installer (MDT-safe)
# - Reads core_apps.config in the same folder as this script
# - Ensures Chocolatey exists (optionally bootstraps via New-ClientReg-Admin.ps1 if present)
# - Installs packages one-by-one with retries
# - Returns 0 (success), 3010 (reboot required), or a real Chocolatey exit code on failure
# -------------------------------------------------------------------------------------------------

# --- Logging (Transcript) ------------------------------------------------------------------------
$logRoot = 'C:\Windows\Temp\Choco'
New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
$logFile = Join-Path $logRoot ("ChocolateyCoreAppsInstall_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date))

try { Start-Transcript -Path $logFile -Append | Out-Null } catch { }

function Write-Log {
param(
[Parameter(Mandatory)][string]$Message
)
Write-Host $Message
}

function Resolve-ChocoExe {
$candidates = @()

if ($env:ProgramData) {
$c1 = Join-Path $env:ProgramData 'chocolatey\bin\choco.exe'
if (Test-Path -LiteralPath $c1) { $candidates += $c1 }
}
if ($env:ChocolateyInstall) {
$c2 = Join-Path $env:ChocolateyInstall 'bin\choco.exe'
if (Test-Path -LiteralPath $c2) { $candidates += $c2 }
}

if ($candidates.Count -gt 0) { return $candidates[0] }

$cmd = Get-Command choco -ErrorAction SilentlyContinue
if ($cmd -and (Test-Path -LiteralPath $cmd.Source)) { return $cmd.Source }

return $null
}

function Get-CorePackagesFromConfig {
param(
[Parameter(Mandatory)][string]$ConfigPath
)

if (-not (Test-Path -LiteralPath $ConfigPath)) {
throw "Core apps config not found: $ConfigPath"
}

[xml]$xml = Get-Content -LiteralPath $ConfigPath -Encoding UTF8

if (-not $xml.packages -or -not $xml.packages.package) {
throw "No <package id=""..."" /> entries were found in: $ConfigPath"
}

$ids = @()
foreach ($p in $xml.packages.package) {
if ($p.id -and ($p.id.Trim().Length -gt 0)) {
$ids += $p.id.Trim()
}
}

$ids = $ids | Select-Object -Unique
if ($ids.Count -eq 0) { throw "Config contained no valid package IDs: $ConfigPath" }

return $ids
}

function Invoke-ChocoInstall {
param(
[Parameter(Mandatory)][string]$ChocoExe,
[Parameter(Mandatory)][string]$PackageId,
[int]$MaxAttempts = 2
)

for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
Write-Log ("[INFO] Installing '{0}' (attempt {1}/{2})" -f $PackageId, $attempt, $MaxAttempts)

# Keep output controlled for MDT logs; no progress bar.
& $ChocoExe install $PackageId -y --no-progress --limit-output
$code = [int]$LASTEXITCODE

if ($code -eq 0) {
Write-Log ("[OK] '{0}' installed/verified successfully." -f $PackageId)
return 0
}

if ($code -eq 3010) {
Write-Log ("[OK] '{0}' installed; reboot required (3010)." -f $PackageId)
return 3010
}

Write-Log ("[WARN] '{0}' returned exit code {1}." -f $PackageId, $code)

# Retry only if attempts remain
if ($attempt -lt $MaxAttempts) {
Start-Sleep -Seconds 5
continue
}

# No more retries -> fail with the real Chocolatey exit code
throw "Package '$PackageId' failed after $MaxAttempts attempts (exit code $code)."
}
}

try {
Write-Log ("[INFO] Log: {0}" -f $logFile)

$configPath = Join-Path $PSScriptRoot 'core_apps.config'

# --- Ensure Chocolatey exists (bootstrap if needed) ------------------------------------------
$chocoExe = Resolve-ChocoExe
if (-not $chocoExe) {
Write-Log "[WARN] choco.exe not found. Attempting bootstrap via New-ClientReg-Admin.ps1 (if present)."

$bootstrapCandidates = @(
(Join-Path $PSScriptRoot 'New-ClientReg-Admin.ps1'),
(Join-Path $PSScriptRoot 'New-ClientReg-Admin-ProGet.ps1')
)

$bootstrap = $bootstrapCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if (-not $bootstrap) {
throw "Chocolatey is missing and no bootstrap script was found beside this installer."
}

Write-Log ("[INFO] Bootstrapping Chocolatey: {0}" -f $bootstrap)
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootstrap
$bCode = [int]$LASTEXITCODE
if ($bCode -ne 0 -and $bCode -ne 3010) {
throw "Bootstrap failed with exit code $bCode."
}

$chocoExe = Resolve-ChocoExe
if (-not $chocoExe) {
throw "Bootstrap completed but choco.exe is still not available."
}

Write-Log ("[OK] Chocolatey found at: {0}" -f $chocoExe)
} else {
Write-Log ("[OK] Chocolatey found at: {0}" -f $chocoExe)
}

# --- Read config and install packages --------------------------------------------------------
$packages = Get-CorePackagesFromConfig -ConfigPath $configPath
Write-Log ("[INFO] Core apps from config: {0}" -f ($packages -join ', '))

$needsReboot = $false

foreach ($pkg in $packages) {
$code = Invoke-ChocoInstall -ChocoExe $chocoExe -PackageId $pkg -MaxAttempts 2
if ($code -eq 3010) { $needsReboot = $true }
}

if ($needsReboot) {
Write-Log "[INFO] One or more installs requested a reboot."
exit 3010
}

Write-Log "[OK] Core apps installation completed successfully."
exit 0
}
catch {
Write-Log ("[ERROR] {0}" -f $_.Exception.Message)
exit 1
}
finally {
try { Stop-Transcript | Out-Null } catch { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="core_apps_regedit" />
<package id="adobe" />
<package id="office36564bit" />
<package id="google" />
<package id="teams" />
<package id="standardshortcuts" />
</packages>
Binary file not shown.