function GetDynamicParamDictionary { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [string] $CommandName ) begin { # Get a list of params that should be ignored (they're common to all advanced functions) $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name } process { # Create the dictionary that this scriptblock will return: $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Convert to object array and get rid of Common params: (Get-Command $CommandName | Select-Object -exp Parameters).GetEnumerator() | Where-Object { $CommonParameterNames -notcontains $_.Key } | ForEach-Object { $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( $_.Key, $_.Value.ParameterType, $_.Value.Attributes ) $DynParamDictionary.Add($_.Key, $DynamicParameter) } # Return the dynamic parameters return $DynParamDictionary } } function ParameterDefinitions { param( [Parameter(ValueFromRemainingArguments=$true,Position = 1)][string[]] $opts ) } function psmain { [CmdletBinding()] #Empty param block (extra params can be added) param( [Parameter(Mandatory=$false, Position = 0)][string] $action = "" ) dynamicparam { if ($action -eq 'list') { $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "listruntime" Mandatory = $false } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'remote', [switch], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('remote', $dynParam1) return $paramDictionary } elseif ($action -eq 'fetch') { #GetDynamicParamDictionary ParameterDefinitions $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "fetchruntime" Mandatory = $false Position = 1 } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'runtime', [string], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('runtime', $dynParam1) return $paramDictionary } elseif ($action -eq 'run') { #GetDynamicParamDictionary ParameterDefinitions $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "runargs" Mandatory = $false ValueFromRemainingArguments = $true } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'opts', [string[]], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('opts', $dynParam1) return $paramDictionary } else { #accept all args when action is unrecognised - so we can go to help anyway $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "invalidaction" Mandatory = $false ValueFromRemainingArguments = $true } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'opts', [string[]], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('opts', $dynParam1) return $paramDictionary } } process { #Called once - we get a single item being our PSBoundParameters dictionary #write-host "Bound Parameters:$($PSBoundParameters.Keys)" switch ($PSBoundParameters.keys) { 'action' { write-host "got action " $PSBoundParameters.action Set-Variable -Name $_ -Value $PSBoundParameters."$_" $known_actions = @("fetch", "list", "run") if (-not($known_actions -contains $action)) { write-host "fetch '$action' not understood. Known_actions: $known_actions" exit 1 } } 'opts' { # write-warning "Unused parameters: $($PSBoundParameters.$_)" } Default { # write-warning "Unhandled parameter -> [$($_)]" } } #foreach ($boundparam in $PSBoundParameters.Keys) { # write-host "k: $boundparam" #} } end { # PSBoundParameters #write-host "action:'$action'" $outbase = $PSScriptRoot $outbase = Resolve-Path -Path $outbase #expected script location is the bin folder of a punk project $rtfolder = Join-Path -Path $outbase -ChildPath "runtime" #Binary artifact server url. (git is not ideal for this - but will do for now - todo - use artifact system within gitea?) $artifacturl = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" switch ($action) { 'fetch' { $arch = "win32-x86_64" $archfolder = Join-Path -Path $rtfolder -ChildPath "$arch" $archurl = "$artifacturl/$arch" $sha1url = "$archurl/sha1sums.txt" $runtime = "tclsh902z.exe" foreach ($boundparam in $PSBoundParameters.Keys) { write-host "fetchopt: $boundparam $($PSBoundParameters[$boundparam])" } if ( $PSBoundParameters["runtime"].Length ) { $runtime = $PSBoundParameters["runtime"] } $fileurl = "$archurl/$runtime" $output = join-path -Path $archfolder -ChildPath $runtime $sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt" $container = split-path -Path $output -Parent new-item -Path $container -ItemType Directory -force #create with intermediary folders if not already present try { Write-Host "Fetching $sha1url" Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop Write-Host "sha1 saved at $sha1local" } catch { Write-Host "An error occurred while downloading ${sha1url}: $($_.Exception.Message)" if ($_.Exception.Response) { Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)" } } if (Test-Path -Path $sha1local -PathType Leaf) { $sha1Content = Get-Content -Path $sha1local $stored_sha1 = "" foreach ($line in $sha1Content) { #all sha1sums have * (binary indicator) - review $match = [regex]::Match($line,"(.*) [*]${runtime}$") if ($match.Success) { $stored_sha1 = $match.Groups[1].Value Write-host "stored hash from sha1sums.txt: $storedhash" break } } if ($stored_sha1 -eq "") { Write-Host "Unable to locate hash for $runtime in $sha1local - Aborting" Write-Host "Please download and verify manually" return } $need_download = $false if (Test-Path -Path $output -PathType Leaf) { Write-Host "Runtime already found at $output" Write-Host "Checking sha1 checksum of local file versus sha1 of server file" $file_sha1 = Get-FileHash -Path "$output" -Algorithm SHA1 if (${file_sha1}.Hash -ne $stored_sha1) { Write-Host "$runtime on server has different sha1 hash - Download required" $need_download = $true } } else { Write-Host "$runtime not found locally - Download required" $need_download = $true } if ($need_download) { Write-Host "Downloading from $fileurl ..." try { Invoke-WebRequest -Uri $fileurl -OutFile "${output}.tmp" -ErrorAction Stop Write-Host "Runtime saved at $output.tmp" } catch { Write-Host "An error occurred while downloading $fileurl $($_.Exception.Message)" if ($_.Exception.Response) { Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)" } return } Write-Host "comparing sha1 checksum of downloaded file with data in sha1sums.txt" Start-Sleep -Seconds 1 #REVIEW - give at least some time for windows to do its thing? (av filters?) $newfile_sha1 = Get-FileHash -Path "${output}.tmp" -Algorithm SHA1 if (${newfile_sha1}.Hash -eq $stored_sha1) { Write-Host "sha1 checksum ok" Move-Item -Path "${output}.tmp" -Destination "${output}" -Force Write-Host "Runtime is available at ${output}" } else { Write-Host "WARNING! sha1 of downloaded file at $output.tmp does not match stored sha1 from sha1sums.txt" return } } else { Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server." Write-Host "No download required" } } else { Write-Host "Unable to consult local copy of sha1sums.txt at $sha1local" if (Test-Path -Path $output -PathType Leaf) { Write-Host "A runtime is available at $output - but we failed to retrieve the list of sha1sums from the server" Write-Host "Unable to check for updated version at this time." } else { Write-Host "Please retry - or manually download a runtime from $archurl and verify checksums" } } } 'run' { #select first (or configured default) runtime and launch, passing arguments $arch = "win32-x86_64" $archfolder = Join-Path -Path $rtfolder -ChildPath "$arch" if (-not(Test-Path -Path $archfolder -PathType Container)) { write-host "No runtimes seem to be installed for $arch`nPlease use 'runtime.cmd fetch' to install" } else { $dircontents = (get-childItem -Path $archfolder -File | where-object Name -Notlike '*_BUILDCOPY.*' | Sort-Object Name) if ($dircontents.Count -gt 0) { #write-host "run.." write-host "num params: $($PSBoundParameters.opts.count)" #todo - use 'active' runtime - need to lookup (PSToml?) #when no 'active' runtime for this os-arch - use last item (sorted in dictionary order) $active = $dircontents[-1].FullName write-host "using: $active" if ($PSBoundParameters.opts.Length -gt 0) { $optsType = $PSBoundParameters.opts.GetType() #method can only be called if .opts is not null write-host "type of opts: $($optsType.FullName)" foreach ($boundparam in $PSBoundParameters.opts) { write-host $boundparam } Write-Host "opts: $($PSBoundParameters.opts)" Write-Host "args: $args" Write-HOst "argscount: $($args.Count)" $arglist = @() foreach ($o in $PSBoundParameters.opts) { $oquoted = $o -replace '"', "`\`"" #$oquoted = $oquoted -replace "'", "`'" if ($oquoted -match "\s") { $oquoted = "`"$oquoted`"" } $arglist += @($oquoted) } $arglist = $arglist.TrimEnd(' ') write-host "arglist: $arglist" #$arglist = $PSBoundParameters.opts Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait } else { #powershell 5.1 and earlier can't accept an empty -ArgumentList value :/ !! #$arglist = @() #Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait #Start-Process -FilePath $active -ArgumentList "" -NoNewWindow -Wait Start-Process -FilePath $active -NoNewWindow -Wait } } else { write-host "No files found in $archfolder" write-host "No runtimes seem to be installed for $arch`nPlease use 'runtime.cmd fetch' to install." } } } 'list' { #todo - option to list for other os-arch $arch = 'win32-x86_64' $archfolder = Join-Path -Path $rtfolder -ChildPath "$arch" $sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt" $archurl = "$artifacturl/$arch" $sha1url = "$archurl/sha1sums.txt" if ( $PSBoundParameters.ContainsKey('remote') ) { if (-not (test-path -Path $archfolder -Type Container)) { new-item -Path $container -ItemType Directory -force #create with intermediary folders if not already present } write-host "Checking for available remote runtimes for" Write-Host "Fetching $sha1url" Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop Write-Host "sha1 saved at $sha1local" $sha1Content = Get-Content -Path $sha1local $remotedict = @{} foreach ($line in $sha1Content) { #all sha1sums have * (binary indicator) - review $match = [regex]::Match($line,"(.*) [*](.*)$") if ($match.Success) { $server_sha1 = $match.Groups[1].Value $server_rt = $match.Groups[2].Value $remotedict[$server_rt] = $server_sha1 } } $localdict = @{} if (test-path -Path $archfolder -Type Container) { $dircontents = (get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY.*' | Where-object {-not ($(".txt",".tm") -contains $_.Extension) }) foreach ($f in $dircontents) { $local_sha1 = Get-FileHash -Path $(${f}.FullName) -Algorithm SHA1 $localdict[$f.Name] = ${local_sha1}.Hash } } Write-host "-----------------------------------------------------------------------" Write-host "Runtimes for $arch" Write-host "Local $archfolder" Write-host "Remote $archurl" Write-host "-----------------------------------------------------------------------" Write-host "Local Remote" Write-host "-----------------------------------------------------------------------" # 12345678910234567892023456789302345 $G = "`e[32m" #Green $Y = "`e[33m" #Yellow $R = "`e[31m" #Red $RST = "`e[m" foreach ($key in $localdict.Keys) { $local_sha1 = $($localdict[$key]) if ($remotedict.ContainsKey($key)) { if ($local_sha1 -eq $remotedict[$key]) { $rhs = "Same version" $C = $G } else { $rhs = "UPDATE AVAILABLE" $C = $Y } } else { $C = $R $rhs = "(not listed on server)" } #ansi problems from cmd.exe not in windows terminal - review $C = "" $RST = "" $lhs = "$key".PadRight(35, ' ') write-host -nonewline "${C}${lhs}${RST}" write-host $rhs } $lhs_missing = "-".PadRight(35, ' ') foreach ($key in $remotedict.Keys) { if (-not ($localdict.ContainsKey($key))) { write-host -nonewline $lhs_missing write-host $key } } Write-host "-----------------------------------------------------------------------" } else { if (test-path -Path $archfolder -Type Container) { Write-host "-----------------------------------------------------------------------" Write-Host "Local runtimes for $arch" $dircontents = (get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY.*' | Where-object {-not ($(".txt",".tm") -contains $_.Extension) }) write-host "$(${dircontents}.count) files in $archfolder" Write-host "-----------------------------------------------------------------------" foreach ($f in $dircontents) { write-host $f.Name } Write-host "-----------------------------------------------------------------------" Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" } else { write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'runtime.cmd fetch' to install." write-host "Use 'runtime.cmd list -remote' to see available runtimes for $arch" } } } default { $actions = @("fetch", "list", "run") write-host "Available actions: $actions" write-host "received" foreach ($boundparam in $PSBoundParameters.opts) { write-host $boundparam } } } return $PSBoundParameters } } #write-host (psmain @args) #$returnvalue = psmain @args #Write-Host "Function Returned $returnvalue" -ForegroundColor Cyan #return $returnvalue psmain @args | out-null exit 0