Overview
Prerequisites Running the script Script
powershell installer
Written: Mar-2026

Windows MF-Installer

So at Methodfish, we're not Git and do not use composer to install our files - but if you did want to get something similar then you can use the following powershell script.

If the current version of the download files are the same in the local folders then no changes will be applied; if they are different then the old version will be renamed to a bak copy.

The script will also collect installations in the ...wamp/project/.mf-installed.json

Once first installed, to update all installs to get the latest - simply use mf-install -update

Prerequisites

I would recommend you set up a path to a safe folder using your System Properties - Windows Environment Variables popup and set your Path; I use c:/wamp/bin

Running the script

powershell :: command line
> cd c:/wamp/www/myproject
> mf-install https://methodfish.com/Download/MFHourglass/files/latest/js/mf-hourglass.js

Script

powershell ::  c:/wamp/bin/mf-install.ps1 file
#--
# mf-install.ps1
#
# Downloads JS and CSS files into project js/css folders
# Tracks installed files in .mf-installed.json
#
# Usage:  
#   set your system path to include this file folder
#   > cd project-folder
#   > mf-install https://methodfish.com/Download/MFHourglass/files/latest/js/mf-hourglass.js
#
#   or
#
#   > mf-install -Update     # refresh all tracked files
#
#   Optional: -Trace for debug
#--

param(
    [string]$JSURL,
    [switch]$Trace,
    [switch]$Update
)

#--
function Trace($msg, $Color="Cyan") {
    if ($Trace) { Write-Host "[TRACE] $msg" -ForegroundColor $Color }
}

#--
function BackupIfChanged($Dir, $File, $NewFile) {
    $FullPath = Join-Path $Dir $File
    if (Test-Path $FullPath) {
        # Compare hashes
        $OldHash = Get-FileHash -Path $FullPath -Algorithm SHA256
        $NewHash = Get-FileHash -Path $NewFile -Algorithm SHA256

        if ($OldHash.Hash -ne $NewHash.Hash) {
            $DateStamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $Backup = "$File.$DateStamp.bak"
            Write-Host "- Backing up changed file: $FullPath" -ForegroundColor Green
            Copy-Item -Path $FullPath -Destination (Join-Path $Dir $Backup) -Force
            Move-Item -Path $NewFile -Destination $FullPath -Force
            Write-Host "- File updated" -ForegroundColor Green
        } else {
            Write-Host "- File unchanged - no changes saved" -ForegroundColor Yellow
            Remove-Item -Path $NewFile -Force
        }
    } else {
        # File does not exist → just move new file into place
        Move-Item -Path $NewFile -Destination $FullPath -Force
        Write-Host "- New file saved: $File" -ForegroundColor Green
    }
}

#--
function DownloadAndValidate($URL, $TargetDir, $FileName) {
    $TmpFile = [System.IO.Path]::Combine($env:TEMP, "mfinstall_$([guid]::NewGuid()).tmp")

    Write-Host "- Downloading $URL" -ForegroundColor Green
    Trace "Downloading to temp file $TmpFile"

    try {
        Invoke-WebRequest -Uri $URL -OutFile $TmpFile -UseBasicParsing
    } catch {
        Write-Host "ERROR: Download failed for $URL" -ForegroundColor Red
        return
    }

    $LineCount = (Get-Content $TmpFile | Measure-Object -Line).Lines
    Trace "Line count = $LineCount"

    if ($LineCount -le 5) {
        Write-Host "WARNING: $FileName is only $LineCount lines" -ForegroundColor Yellow
        Get-Content $TmpFile
    }

    # Backup and deploy if changed
    BackupIfChanged $TargetDir $FileName $TmpFile

    # Update tracking file
    $RelativePath = Join-Path ($TargetDir.Replace("$PubHtml\", "")) $FileName
    $Entry = $Tracked.files | Where-Object { $_.url -eq $URL }
    if ($Entry) {
        $Entry.lastDownloaded = Get-Date -Format "yyyyMMdd_HHmmss"
    } else {
        $Tracked.files += [pscustomobject]@{
            url = $URL
            local = $RelativePath
            lastDownloaded = Get-Date -Format "yyyyMMdd_HHmmss"
        }
    }
    SaveTracking
}

#--
# Locate project public_html
#--
$CurDir = Get-Location
Trace "Starting search from $CurDir"

do {
    if (Test-Path "$CurDir\public_html") {
        $PubHtml = "$CurDir\public_html"
        break
    }

    $Parent = Split-Path $CurDir -Parent
    if ([string]::IsNullOrEmpty($Parent) -or $Parent -eq $CurDir) {
        Write-Host "ERROR: Not inside a WAMP project (no public_html found)" -ForegroundColor Red
        exit 1
    }
    $CurDir = $Parent
} while ($true)

Write-Host "- Project folder: $PubHtml" -ForegroundColor Green

$JsDir  = Join-Path $PubHtml "js"
$CssDir = Join-Path $PubHtml "css"
New-Item -ItemType Directory -Path $JsDir -Force | Out-Null
New-Item -ItemType Directory -Path $CssDir -Force | Out-Null

#--
# Tracking file
#--
$TrackingFile = Join-Path $PubHtml "../.mf-installed.json"

if (Test-Path $TrackingFile) {
    $Tracked = Get-Content $TrackingFile | ConvertFrom-Json
} else {
    $Tracked = @{ files = @() }
}

function SaveTracking() {
    $Tracked | ConvertTo-Json -Depth 5 | Set-Content -Path $TrackingFile
}

#--
# Update all tracked files if requested
#--
if ($Update) {
    Write-Host "- Update mode: refreshing all tracked files..." -ForegroundColor Green
    foreach ($file in $Tracked.files) {
        $LocalFullPath = Join-Path $PubHtml $file.local
        $TargetDir = Split-Path $LocalFullPath -Parent
        $FileName = Split-Path $LocalFullPath -Leaf
        DownloadAndValidate $file.url $TargetDir $FileName
    }
    Write-Host "- Update complete" -ForegroundColor Green
    exit
}

#--
# Install single file
#--
$JSFile = [System.IO.Path]::GetFileName($JSURL)
if (-not $JSFile.ToLower().EndsWith(".js")) { $JSFile += ".js" }

$CSSURL = $JSURL -replace '/js/', '/css/' -replace '\.js$', '.css'
$CSSFile = [System.IO.Path]::GetFileName($CSSURL)
if (-not $CSSFile.ToLower().EndsWith(".css")) { $CSSFile += ".css" }

DownloadAndValidate $JSURL $JsDir $JSFile
Write-Host "" -ForegroundColor Green
DownloadAndValidate $CSSURL $CssDir $CSSFile
Write-Host "" -ForegroundColor Green
Write-Host "- Distribution complete" -ForegroundColor Green

square