PowerShell – Copy and remove old files for installation for MECMcache

Just another Tech site

PowerShell – Copy and remove old files for installation for MECMcache

Below is the updated PowerShell 5.1 script that accepts -AppName, -Version, and -SourceFile, unblocks the source file before copying it to C:\Windows\_MECMCache\$AppName\$Version, creates directories as needed, removes version folders older than 60 days, and logs actions and errors to a file and the Windows Application event log.

 

<#
.SYNOPSIS
  Copy a file into MECM cache, clean old versions, unblock before copy, and log events.

.DESCRIPTION
  Accepts parameters -AppName, -Version, -SourceFile.
  If -SourceFile is not provided, the script looks for "fileA" in the script root.
  Requires PowerShell 5.1. Run as Administrator to create event source and write to C:\Windows.

.EXAMPLE
  .\Install-Cache.ps1 -AppName App1 -Version 1.0.0.0 -SourceFile "C:\temp\fileA.exe"
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$AppName,

    [Parameter(Mandatory=$true)]
    [string]$Version,

    [Parameter(Mandatory=$false)]
    [string]$SourceFile
)

# === Configuration ===
$cacheRoot = "C:\Windows\_MECMCache"
$daysToKeep = 60
$defaultFileName = "fileA"

# === Derived paths ===
$destRoot = Join-Path -Path $cacheRoot -ChildPath $AppName
$destVersionPath = Join-Path -Path $destRoot -ChildPath $Version
$logDir = Join-Path -Path $destRoot -ChildPath "Logs"
$logFile = Join-Path -Path $logDir -ChildPath "install.log"
$eventSource = "MECMCacheInstaller-$AppName"

# === Helper functions ===
function Ensure-LogDirectory {
    try {
        if (-not (Test-Path -Path $logDir)) {
            New-Item -Path $logDir -ItemType Directory -Force | Out-Null
        }
    } catch {
        Write-Host "Failed to create log directory $logDir : $($_.Exception.Message)" -ForegroundColor Red
    }
}

function Write-Log {
    param(
        [string]$Level,
        [string]$Message
    )
    $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    $line = "$timestamp`t[$Level]`t$Message"
    try {
        Ensure-LogDirectory
        Add-Content -Path $logFile -Value $line -ErrorAction Stop
    } catch {
        Write-Host "Unable to write to log file $logFile : $($_.Exception.Message)" -ForegroundColor Yellow
    }

    try {
        if (-not [System.Diagnostics.EventLog]::SourceExists($eventSource)) {
            New-EventLog -LogName Application -Source $eventSource
        }
        switch ($Level.ToUpper()) {
            "ERROR"   { Write-EventLog -LogName Application -Source $eventSource -EntryType Error -EventId 1001 -Message $Message }
            "WARNING" { Write-EventLog -LogName Application -Source $eventSource -EntryType Warning -EventId 1002 -Message $Message }
            default   { Write-EventLog -LogName Application -Source $eventSource -EntryType Information -EventId 1000 -Message $Message }
        }
    } catch {
        $errMsg = "EventLog write failed: $($_.Exception.Message)"
        try { Add-Content -Path $logFile -Value "$timestamp`t[WARN]`t$errMsg" } catch {}
    }
}

function Write-Info { param($m) Write-Log -Level "INFO" -Message $m }
function Write-Warn { param($m) Write-Log -Level "WARNING" -Message $m }
function Write-ErrorLog { param($m) Write-Log -Level "ERROR" -Message $m }

# === Resolve source path ===
try {
    if ($SourceFile) {
        if (Test-Path -Path $SourceFile -PathType Leaf) {
            $sourcePath = (Resolve-Path -Path $SourceFile).ProviderPath
        } else {
            if ($PSScriptRoot) {
                $candidate = Join-Path -Path $PSScriptRoot -ChildPath $SourceFile
                if (Test-Path -Path $candidate -PathType Leaf) {
                    $sourcePath = (Resolve-Path -Path $candidate).ProviderPath
                } else {
                    throw "SourceFile specified but not found: $SourceFile"
                }
            } else {
                throw "SourceFile specified but not found and script root is unavailable: $SourceFile"
            }
        }
    } else {
        if ($PSScriptRoot) {
            $candidate = Join-Path -Path $PSScriptRoot -ChildPath $defaultFileName
            if (Test-Path -Path $candidate -PathType Leaf) {
                $sourcePath = (Resolve-Path -Path $candidate).ProviderPath
            } else {
                throw "No SourceFile provided and default file not found in script root: $candidate"
            }
        } else {
            throw "No SourceFile provided and script root is unavailable."
        }
    }
    Write-Info "Resolved source file: $sourcePath"
} catch {
    $msg = "Source resolution failed: $($_.Exception.Message)"
    Write-ErrorLog $msg
    Write-Host $msg -ForegroundColor Red
    exit 2
}

# === Unblock source file before copying ===
try {
    # Only attempt Unblock-File if the cmdlet exists (PowerShell 3+)
    if (Get-Command -Name Unblock-File -ErrorAction SilentlyContinue) {
        try {
            Unblock-File -Path $sourcePath -ErrorAction Stop
            Write-Info "Unblocked source file: $sourcePath"
        } catch {
            $uMsg = "Failed to unblock file $sourcePath : $($_.Exception.Message)"
            Write-Warn $uMsg
            # continue; copying may still succeed depending on ACLs/zone info
        }
    } else {
        Write-Warn "Unblock-File cmdlet not available on this system; skipping unblock step."
    }
} catch {
    Write-Warn "Unexpected error during unblock step: $($_.Exception.Message)"
}

# === Main operations ===
try {
    Write-Info "Script started. App='$AppName' Version='$Version' Source='$sourcePath'"

    # Ensure destination directory exists
    try {
        if (-not (Test-Path -Path $destVersionPath)) {
            New-Item -Path $destVersionPath -ItemType Directory -Force | Out-Null
            Write-Info "Created destination directory: $destVersionPath"
        } else {
            Write-Info "Destination directory already exists: $destVersionPath"
        }
    } catch {
        $msg = "Failed to create destination directory $destVersionPath : $($_.Exception.Message)"
        Write-ErrorLog $msg
        throw $msg
    }

    # Copy the file
    try {
        $destFile = Join-Path -Path $destVersionPath -ChildPath (Split-Path -Path $sourcePath -Leaf)
        Copy-Item -Path $sourcePath -Destination $destFile -Force -ErrorAction Stop
        Write-Info "Copied '$sourcePath' to '$destFile'"
    } catch {
        $msg = "Copy failed: $($_.Exception.Message)"
        Write-ErrorLog $msg
        throw $msg
    }

    # Clean up old version folders for this app older than $daysToKeep days
    try {
        if (Test-Path -Path $destRoot) {
            $threshold = (Get-Date).AddDays(-1 * [int]$daysToKeep)
            $currentFull = (Get-Item -LiteralPath $destVersionPath).FullName
            $folders = Get-ChildItem -Path $destRoot -Directory -ErrorAction Stop |
                       Where-Object { $_.FullName -ne $currentFull } |
                       Where-Object {
                           $time = if ($_.CreationTime -ne $null) { $_.CreationTime } else { $_.LastWriteTime }
                           $time -lt $threshold
                       }

            if ($folders.Count -gt 0) {
                foreach ($f in $folders) {
                    try {
                        Remove-Item -Path $f.FullName -Recurse -Force -ErrorAction Stop
                        Write-Info "Removed old version folder: $($f.FullName)"
                    } catch {
                        Write-Warn "Failed to remove folder $($f.FullName) : $($_.Exception.Message)"
                    }
                }
            } else {
                Write-Info "No old version folders older than $daysToKeep days found for $AppName"
            }
        } else {
            Write-Info "No app root folder found at $destRoot; nothing to clean."
        }
    } catch {
        Write-Warn "Cleanup step failed: $($_.Exception.Message)"
    }

    Write-Info "Script completed successfully."
    exit 0
}
catch {
    $err = $_.Exception.Message
    Write-ErrorLog "Unhandled error: $err"
    Write-ErrorLog "StackTrace: $($_.Exception.StackTrace)"
    Write-Host "Unhandled error: $err" -ForegroundColor Red
    exit 1
}

Notes and recommendations

  • Unblock behavior: the script calls Unblock-File if available and logs success or warning on failure; copying proceeds even if unblock fails (logged).
  • Run as Administrator to allow creation of the event source and writing to C:\Windows.
  • If fileA has an extension, pass it via -SourceFile or change $defaultFileName.
  • To change retention, modify $daysToKeep.