Skip to content

Instantly share code, notes, and snippets.

@BananaAcid
Last active August 21, 2026 18:00
Show Gist options
  • Select an option

  • Save BananaAcid/1dc9117571967b26ceabc972009137ae to your computer and use it in GitHub Desktop.

Select an option

Save BananaAcid/1dc9117571967b26ceabc972009137ae to your computer and use it in GitHub Desktop.
Install PowerShellGSM from commandline without git, and setup basics by prompts (with default options)

PowerShellGSM installation script

image

Usage

Copy and paste the below one-liner into your powershell console (WIN+R, then "powershell.exe")

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://gist.githubusercontent.com/bananaacid/1dc9117571967b26ceabc972009137ae/raw/install_PowerShellGSM.ps1'))

What it does

It is supposed to lead through the basic download and prep steps (of PowerShellGSM by patrix87)

  1. This does run the install_PowerShellGSM.ps1 script below
  2. Downloads (and asks to update) the PowerShellGSM powershell script files *1
  3. Asks a few basic questions to configure the PowerShellGSM installation:
    • like where to save the server
    • what server template to use
    • what to change the passwords in the created config of the selected server template to
    • if you want to open the configuration file to be able to edit

*1 (preserves your server configs by design)

*You may skip any step in the process, to prepare a new server, a second, ... *

What it doesn't

This is not meant to configure the servers individually, nor to provide any UI to PowerShellGSM.

That means: you still have to run the C:\server\games\PowerShellGSM\launchers\yourgameserver.cmd after PowerShellGSM is installed to start installing the newly configured game server, and to launch it.

Any other configuration is as per documentation from the PowerShellGSM.

Note

Powershell 7 (pwsh.exe) works fine (v7.0+).
Powershell 5 (powershell.exe) works fine (v5.1+).

<#
BananaAcid 2022-2026
License MIT
v2.0.0
To run this file and get PowerShellGSM installed, paste the following into your powershell 5 (not powershell 7+ [posh.exe]):
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://gist.githubusercontent.com/bananaacid/1dc9117571967b26ceabc972009137ae/raw/install_PowerShellGSM.ps1'))
Changes:
- 21.08.2026, 2.0.0 added newer Menu function, some coloring, some fixes to fully work with PS 5+ and 7+
- 14.06.2024, 1.1.0 added version selection for PowershellGSM
- 16.02.2024, 1.0.3 fix for PalWorld config: parsed the replacement block (Sart-ServerProp) as well
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '', Scope = 'Function', Target = '*')] # allow aliases in linter
[CmdletBinding()] # allow -Debug
Param() # no parameters, but required
Function Main() {
# Just to make sure. For me it was optional.
Add-Type -Assembly System.IO.Compression.FileSystem
# GIT head URL
$url = "https://github.com/patrix87/PowerShellGSM/archive/refs/heads/main.zip"
$tmpFile = "$env:TEMP\PowerShellGSM.main.zip";
# GIT releases JSON endpoint URL:
$releasesUrl = "https://api.github.com/repos/patrix87/PowerShellGSM/releases"
$lastState = Get-Content ~\.PowerShellGSM -erroraction 'silentlycontinue' | ConvertFrom-Json -erroraction 'silentlycontinue'
if ( -not $? ) {
$lastState = [pscustomobject]@{
exPath = "";
lastVersion = "main"
}
}
else {
## MIGRATION from v1.0.3
if ($null -eq $lastState.lastVersion) {
$newState = [pscustomobject]@{
exPath = $lastState.exPath;
lastVersion = "main"
}
$lastState = $newState
}
echo "PowershellGSM Installation (Version $(ConvertTo-CreateMenuAnsiColorString -InputString $lastState.lastVersion -ForegroundColor Green)) found in: $(ConvertTo-CreateMenuAnsiColorString -InputString $lastState.exPath -ForegroundColor Blue)"
}
## DOWNLOAD
if (Test-Path ($tmpFile -Replace '.main.',".$($lastState.lastVersion).") ) {
$r = Read-Host "Download fresh PowerShellGSM files (Y/n)"
if ($r -ne "" -and $r -ne "y" -and $r -ne "Y") {
$noDl = $True
}
}
if ($noDl -ne $True) {
## SELECT VERSION
# get the github releases content
$releases = Invoke-RestMethod -Uri $releasesUrl
# prepare version selection
$MenuOptions = @("[no update]", "cutting edge") + ($releases |% { $_.tag_name })
# ask the user to select a version
$releaseVersionSelection = New-SelectionMenu -Title "`nSelect PowerShellGSM version to install:`n" -MenuOptions $MenuOptions -Columns "auto" -Selected 2 # -CleanHost $True
$releaseVersion = $MenuOptions[$releaseVersionSelection]
if ($releaseVersion -eq "[no update]") {
$noDl = $True;
}
elseif (($releaseVersion -eq "cutting edge") -or ($null -eq$releaseVersion)) {
# download head as previous
$releaseVersion = "main"
}
else {
# get the url of the new
$url = $releases |? { $_.tag_name -eq $releaseVersion } |% { $_.zipball_url }
$tmpFile = $tmpFile -Replace '.main.',".$releaseVersion."
}
## DOWNLOAD
if ($noDl -ne $True) {
echo "downloading $releaseVersion from $url"
Invoke-WebRequest $url -OutFile $tmpFile
# get the folder of the element
$zipSubFolder = [IO.Compression.ZipFile]::OpenRead($tmpFile).Entries.FullName |? { $_ -match [regex]"patrix87-PowerShellGSM-[^\/]+\/$" } |select -first 1
## EXTRACT
if ($lastState.exPath -ne "") { $def = "to " + $lastState.exPath } else { $def = "here" }
$r = Read-Host "Extract installation file $def (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
if ($lastState.exPath -ne "") { $r = $lastState.exPath } else { $r = pwd }
}
else {
$r = Read-Host "Installation path"
}
$exPath = $r
echo "Installation path used: $exPath"
Expand-Archive $tmpFile $env:TEMP -Force
ni $exPath -ItemType Directory -Force >$null
cp "$env:TEMP\$zipSubFolder\*" $exPath\ -Force -R
rm "$env:TEMP\$zipSubFolder" -Force -R
$lastState.lastVersion = $releaseVersion
}
else {
$exPath = $lastState.exPath
}
}
else {
if ($lastState.exPath -ne "") { $def = $lastState.exPath } else { $def = "here" }
$r = Read-Host "Enter path to previous installation (default: $def)"
if ($r -eq "") {
if ($lastState.exPath -ne "") { $r = $lastState.exPath } else { $r = pwd }
}
$exPath = $r
}
## ACTIVATE TEMPLATE
# check if path is a reparse point: Symlink/Hardlink
function Test-ReparsePoint([string]$path) {
$file = Get-Item $path -Force -ea SilentlyContinue
return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
}
$Configs = ls $exPath\configs |? {$_.Name -notlike '#*' -and $_.Name -notlike '*.backup.*'} |% BaseName
Write-Host "Existing configs:" -ForegroundColor Yellow
$list = ConvertTo-CreateMenuAnsiColorString -InputString $($Configs -join ", ") -ForegroundColor Green
echo "$list`n"
$Options = ls $exPath\templates |? {$_.Name -notlike '#*'} |% BaseName
$Options = @("[no template]") + $Options
$OptionsCaptions = $Options |% { $_ -in $Configs ? "·$_" : $_ }
$Selection = New-SelectionMenu -Title "Select a server template to add or edit:`n" -MenuOptions $OptionsCaptions -Columns "auto" -MaximumColumnWidth 20 -ShowCurrentSelection $True
$templateName = $Options[$Selection]
$cfgFile = "$templateName.psm1"
$cfgFileBackup = $null
if ($Selection -eq 0) {
continue
}
if (Test-Path $exPath\configs\$cfgFile) {
$r = Read-Host "Overwrite the config file for the selected server with new and blank one (backup will be created) (y/N)"
if ($r -eq "" -or $r -eq "n" -or $r -eq "N") {
$ignoreReplace = $True
}
else {
# just to make sure ...
$timestamp = Get-Date -format "yyyy-MM-dd_HH-mm-ss"
cp "$exPath\configs\$cfgFile" "$exPath\configs\$cfgFile.backup.$timestamp" -Force
$cfgFileBackup = "$exPath\configs\$cfgFile.backup.$timestamp"
if (-not (Test-ReparsePoint "$exPath\launchers\$templateName.cmd")) {
cp "$exPath\launchers\$templateName.cmd" "$exPath\launchers\$templateName.cmd.backup.$timestamp"
}
}
}
if ($ignoreReplace -ne $True) {
cp "$exPath\templates\$cfgFile" "$exPath\configs" -Force
if ($PSVersionTable.PSVersion.Major -ge 5) {
# use symlink, if possible
New-Item -Path "$exPath\launchers\$templateName.cmd" -ItemType SymbolicLink -Value "$exPath\launchers\run.cmd" -Force | Out-Null
}
else {
cp "$exPath\launchers\run.cmd" "$exPath\launchers\$templateName.cmd" -Force
}
}
Write-Host "To run the selected server for $templateName, run $exPath\launchers\$templateName.cmd`n" -ForegroundColor Yellow
## change passwords
$r = Read-Host "Change all passwords in the new config (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
function C1($str) {
return ConvertTo-CreateMenuAnsiColorString -InputString $str -ForegroundColor blue
}
function C2($str) {
return ConvertTo-CreateMenuAnsiColorString -InputString $str -ForegroundColor green
}
# get content
$inputFile = gc $exPath\configs\$cfgFile -Raw
# find description, key, value (password)
$results = [Regex]::Matches($inputFile, "^[\s]+(?<desc>#.*)*\n^[\s]+(?<line>(?<key>[^#'\s]*Password[^=\s]*)[\s]*=[\s]*""(?<val>[^""]*)"")", "IgnoreCase,Multiline")
$newFile = $inputFile
For ($i = 0; $i -lt $results.count; $i++) {
# ask for new password and show comment + old value
Write-Host "$($results[$i].Groups['desc'].Value) -- currently: '$($results[$i].Groups['val'].Value)'" -ForegroundColor DarkGray
$val = Read-Host "New password for '$(C1( $results[$i].Groups['key'].Value) )' (empty for random)"
if ($val -eq "") {
$val = ((-join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 8 |%{[char]$_})) -replace '0|O|o|I|l|1|5|S|s','A').ToLower() # if not toLower, add: |Y|y|V|v|K|k|W|w|P|p|C|c|X|x
Write-Host "... random: $(C2($val))"
}
# replace old value in line
$a = '"' + $results[$i].Groups["val"].Value + '"'
$b = '"' + $val + '"'
$x = $results[$i].Groups["line"].Value -replace $a,$b
# replace old row
$newFile = $newFile -replace $results[$i].Groups["line"].Value,$x
}
Write-Host ""
# write new content
$newFile | Out-File $exPath\configs\$cfgFile
}
## OPEN GAME CONFIGS
$gameConfigFolderPath = gc $exPath\configs\$cfgFile | Select-String -Pattern "ConfigFolder\s*=\s*`"(?<path>.*)`"" |% { $_.Matches[0].Groups['path'].Value }
$gameConfigFolderPath = $gameConfigFolderPath -ireplace '\$Name',$templateName
$gameConfigFolderPath = [System.IO.Path]::GetFullPath((Join-Path $PWD $gameConfigFolderPath)) # path does not need to exist
Write-Host "The game config folder is: $gameConfigFolderPath" -ForegroundColor Yellow
if (Test-Path $gameConfigFolderPath) {
$r = Read-Host "Show game config folder (Y/n)`n"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
explorer "$gameConfigFolderPath"
}
}
Write-Host ""
## SHOW CONFIG
$r = Read-Host "Show config file to be edited manually (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
notepad "$exPath\configs\$cfgFile"
Start-Process -FilePath explorer.exe -ArgumentList "/select,`"$exPath\configs\$cfgFile`""
}
## SHOW FOLDER
$r = Read-Host "Show installation folder (y/N)"
if ($r -eq "y" -or $r -eq "Y") {
explorer "$exPath"
}
## SHOW WEBSITE
$r = Read-Host "Show PowershellGSM Readme (y/N)"
if ($r -eq "y" -or $r -eq "Y") {
start "https://github.com/patrix87/PowerShellGSM/blob/main/README.md"
}
Write-Host ""
## RUN?
$r = Read-Host (ConvertTo-CreateMenuAnsiColorString -InputString "Install/run the selected server for $templateName (y/N)" -ForegroundColor Red)
if ($r -eq "y" -or $r -eq "Y") {
echo "$exPath\launchers\$templateName.cmd"
start "$exPath\launchers\$templateName.cmd" -WorkingDirectory "$exPath\launchers\"
}
## REMEMBER
$lastState.exPath = Convert-Path $exPath
ConvertTo-Json $lastState | Out-File ~\.PowerShellGSM
}
## MODULES
# https://raw.githubusercontent.com/BananaAcid/CreateMenuPS/refs/tags/2.0.2/Create-Menu/Create-Menu.ps1
Function New-SelectionMenu {
<#
.SYNOPSIS
Shows strings as a table to be selectable by navigating with arrow keys
.DESCRIPTION
Author: Nabil Redmann (BananaAcid)
License: ISC
.INPUTS
array of strings
.PARAMETER MenuOptions
Value: <String[]>
Takes an array with selections (must be more then one)
.PARAMETER Title
Value: <Null|ScriptBlock|String>
Takes a string or a scriptblock, use $global:varname to link to Title, Footer or SelectionCallback (available vars: $Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $esc, $CL, $global:*)
.PARAMETER Selected
Value: <Null|Integer>
Initial string to select
.PARAMETER Footer
Value: <Null|ScriptBlock|String>
Takes a string or a scriptblock (available vars: $Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $esc, $CL, $global:*)
.PARAMETER SelectionCallback
Value: <Null|ScriptBlock>
If you want to trigger something on selection or a key, or change the $MenuOptions/$Selection, return $False to exit
# params in: $Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $KeyInput, $esc (char 27, ansi seq. start), $CL (ansicode to clear to end of line)
# return: $False to end, or array of new options and triggers re-calculation of the menu
# Modifiable: $Selection to change the selection, $MenuOptions for new array of options (like returning an array, BUT DOES NOT re-calculate the menu)
.PARAMETER Columns
Value: <"Auto"|Integer>
Define how many columns should be shown (default: "Auto")
.PARAMETER MaximumColumnWidth
Value: <"Auto"|Integer>
The maximum amount of chars in a cell should be displayed, if to large, '...' will be appended
(default: "Auto" if Columns is a number, results in "20" if Columns is "Auto")
.PARAMETER ShowCurrentSelection
Value: <Boolean>
Shows the current selection text in full length in the console title (default: $False)
.PARAMETER PassThrou
Without, will output the index of the selection, otherwise the selected string (default: $False)
.PARAMETER ReturnObject
Returns Selection index, SelectionValue string, MenuOptions string[] of maybe modified items, MenuOptionsInput string[] of input strings, Items object of {"Name" maybe modified,"Index","Input" originial string} -- has a higher priority then PassThrou
.PARAMETER ForegroundColor
Value: <[Console]::ForegroundColor|ConsoleColor>
Color for the selection (default: Black)
.PARAMETER ForegroundColorSelection
Value: <Black|ConsoleColor>
Color for the selection (default: Black)
.PARAMETER BackgroundColorSelection
Value: <Cyan|ConsoleColor>
Color for the selection (default: Cyan)
.PARAMETER ForegroundColorTitle
Value: <Cyan|ConsoleColor>
Color for the title (default: Cyan)
.PARAMETER ForegroundColorFooter
Value: <Black|ConsoleColor>
Color for the footer (default: Black)
.PARAMETER ClearHost
Value: <Boolean>
Will clear the screen on start and after selecting from the terminal (default: False)
.PARAMETER CleanHost
Value: <Boolean>
Will clear the menu after selecting from the terminal (default: False)
.PARAMETER FilterCallback
Value: <Null|ScriptBlock>
Will allows to modify the list of strings before they are shown
# params in: $CellValue, $Current, $CurrentValue, $Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $esc, $CL, $Row, $Column
# $esc = char 27, ansi seq. start
# $CL = ansi seq to clear to end of line
# $CellValue = the cell content, the $SelectedValue with spaces around it
# return: new value for $CellValue (if nothing/$false/$null is returned, it will not be changed)
# Modifiable: $MenuOptions,$Selection,$CellValue
.OUTPUTS
Default is the index of the selected string, using -PassThrou it will be the selected string, using -ReturnObject it will be an object with items
ReturnObject = {
Selection = index of the selection
SelectionValue = string of the selection
MenuOptions = string[] of maybe modified items (was used for display)
MenuOptionsInput = string[] of input strings
Items = {
Name = maybe modified (was used for display)
Index = index of the item within the MenuOptions (Not MenuOptionsInput)
Input = originial string
}
}
.EXAMPLE
import-module ./create-menu.ps1
ls | Create-Menu
show all files as a selection and return its index upon selecting
.EXAMPLE
New-Module -Name "Create Menu" -ScriptBlock ([Scriptblock]::Create((New-Object System.Net.WebClient).DownloadString("https://gist.githubusercontent.com/BananaAcid/b8efca90cc6ca873fa22a7f9b98d918a/raw/Create-Menu.ps1"))) | Out-Null
ls | Create-Menu
show all files as a selection and return its index upon selecting. Loading from remote location.
.EXAMPLE
$check = Create-Menu no,yes "Want it?" 1
Shortest version. "no" first, becuase index 0 is equal to false. the "1" selects index 1 initially
Outputs on single line: `Want it? no yes `
.EXAMPLE
$check = Create-Menu @("no","yes") -Title "Want it?" -Selected 1
Longer version
.EXAMPLE
ls | create-menu -passthrou -T "Show Content:`n" |% {cat $_}
Outputs a filename's contents after selecting
.EXAMPLE
echo "Select a letter"; $sel = @("a","b","c") | Create-Menu -PassThrou; echo "Selected: $sel"
Usage for -MenuOptions by piping in
.EXAMPLE
ls ../ | Create-Menu -Title "abc`n-----"
A simple string as title
.EXAMPLE
ls ../ | Create-Menu -Title {"SEL: $SelectionValue`n-----`n"}
A scriptblock with an internal variable
.EXAMPLE
ls ../ | Create-Menu -Title {"SEL: $SelectionValue`n-----`n"} -ReturnObject
A scriptblock with an internal variable, returning the selection opens
.EXAMPLE
ls ../ | Create-Menu -Title {Write-Host Green "SEL: $SelectionValue`n-----`n"}
A scriptblock with colored title
.EXAMPLE
Create-Menu a,b -SelectionCallback {if ($KeyInput -eq 27) { return $False } }
ESC to cancel input
.EXAMPLE
Create-Menu 0,1,2,3 -t {"SEL: $global:ki`n-----"} -SelectionCallback { $global:ki = $KeyInput }
Show code of pressed key
.EXAMPLE
$YesNoCB = { If ($KeyInput -eq 89) { $Selection = 0 ; Return $True } If ($KeyInput -eq 78) { $Selection = 1 ; Return $True } }
Create-Menu Y,n -SelectionCallback $YesNoCb
Show "Y" and "n", pressing y (89) or n (78) will select the specific index, exit and show the selected
.EXAMPLE
$SpacePressed = {
If ($KeyInput -eq 32) { # space-key
if ($SelectionValue -like '`* *') { # check if item has a '* ' prefix = is selected already
$MenuOptions[$Selection] = $MenuOptionsInput[$Selection] # remove prefix by setting it to the original string
} Else {
$MenuOptions[$Selection] = '* ' + $SelectionValue # add prefix to displayed item
}
}
}
$ret = ls ~/ | Create-Menu -t {"Full Name: $SelectionValue`n-----`n"} -SelectionCallback $SpacePressed -MaximumColumnWidth 40 -ReturnObject
$selected = $ret.Items |? { $_.Name -like '`* *' } |% Input # check all items, if they had been marked (would also work: .Name -ne .Input)
Write-Host "File Paths: ", $selected
Allows to select multiple files with the space key (Keycode 32), then gets their names
.EXAMPLE
$names = "Tom", "Tim", "John", "Alice", "Bob", "Eve", "Adam", "Sarah", "Michael", "Jessica", "William", "Oliver", "Benjamin", "Hannah", "Kevin", "Lily", "David", "Emily", "Matthew", "Ashley", "Joseph", "James", "Laura", "Robert", "Richard", "Patricia", "Christopher", "Nicolas", "Sam", "Jennifer", "Lisa", "Brian", "Heather", "Katherine", "Julia", "Steven", "Amanda", "Rebecca", "Linda", "Daniela", "Elizabeth", "Andrew", "Stephanie", "Anthony", "Rachel", "Michelle", "Joshua", "Samantha", "Emi", "Alex", "Steven", "Amanda", "Rebecca", "Linda", "Daniel", "Elizabeth", "André", "Stephanie", "Anthony", "Rachel", "Michelle", "Joshua", "Sammy", "Amy", "Alexander", "Sammy"
[Collections.ArrayList]$Global:Selected = @() # collect indexes of selected names
Create-Menu $names `
-Title { "Selected Idx: $Global:selected`n-----`n" } `
-Footer { "Selected Names: $( $Global:selected |% { $MenuOptionsInput[$_] } )" } `
-SelectionCallback {
if ($KeyInput -eq 27) { return $False } # exit with ESC-key
If ($KeyInput -eq 32) { # select with space-key
if ($Global:selected -contains $Selection) { # the index is already selected (in the list)
$Global:selected.Remove($Selection) # remove the idx from the list
}
Else {$Global:selected.Add($Selection) } # remember the idx (NOT THE NAME, but the unique idx!)
return $true # add and remove return false - we need to overwrite the return
}
} `
-Filter {
if ($Global:selected -contains $Current) {
$CellValue -Replace $CurrentValue,"$esc[4m$CurrentValue$esc[24m" # highlight selected with ansi sq for underline, BUT preserve cell value spaces
}
} | Out-Null # we only want the $global:Selected, we do not care about the default output
Write-Host "Names: ", ( $Global:selected |% { $names[$_] } )
Allows to select multiple files with the space key, then gets their names. ESC will exit
It uses an external var to keep track of selecting and unselecting, and uses the -Filter to modify the items
.EXAMPLE
$num = Create-Menu (1..10) `
-Title "Select a number:" `
-Filter { $CellValue -Replace $CurrentValue,"<$CurrentValue>" } `
-Clean `
-PassThrou
Write-Host "You selected: ", $num
Show a selection of numbers and reformat them. After selection, the menu will vanish and at its place show the selected number
How?
-Clean = Menu will vanish after selection
-PassThrou = return the selected value, not index
.NOTES
Based on: https://community.spiceworks.com/scripts/show/4785-create-menu-2-0-arrow-key-driven-powershell-menu-for-scripts
#>
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline=$True ,Mandatory=$True )][Alias("Options")][String[]]$MenuOptions,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Object]$Title = $Null,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("Index")][int]$Selected = $Null,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Object]$Footer = $Null,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("CB", "CallbackSelection")][ScriptBlock]$SelectionCallback = $Null,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][String]$Columns = "Auto",
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][String]$MaximumColumnWidth = "Auto",
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][bool]$ShowCurrentSelection = $False,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][switch]$PassThrou = $False,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][switch]$ReturnObject = $False,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("c")][ConsoleColor]$ForegroundColor = [Console]::ForegroundColor,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("cs")][ConsoleColor]$ForegroundColorSelection = [ConsoleColor]::Black,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("csb")][ConsoleColor]$BackgroundColorSelection = [ConsoleColor]::Cyan,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("ct")][ConsoleColor]$ForegroundColorTitle = [ConsoleColor]::Yellow,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias("cf")][ConsoleColor]$ForegroundColorFooter = [ConsoleColor]::Green,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][switch]$CleanHost = $False,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][switch]$ClearHost = $False,
[Parameter(ValueFromPipeline=$False,Mandatory=$False)][Alias('Filter')][Object]$FilterCallback = $Null
)
# in case items were pipelined
$all = @($Input)
If ($all) {
$MenuOptions = [array]$all
}
$MenuOptionsInput = $MenuOptions.psobject.copy() # | ConvertTo-Json -depth 100 | ConvertFrom-Json
$MaxValue = $MenuOptions.count-1
If ($Selected) { $Selection = $Selected } else { $Selection = 0 }
$EnterPressed = $False
$WindowTitleBackup = $Host.UI.RawUI.WindowTitle
$RowQty = 0
$outputBuffer = "" # contains output
If ($Columns -eq "Auto") {
If ($MaximumColumnWidth -eq "Auto") {
$MaximumColumnWidth = 20
}
$WindowWidth = $Host.UI.RawUI.BufferSize.Width
$Columns = [Math]::Floor($WindowWidth / ([int]$MaximumColumnWidth +2))
}
Else {
If ($MaximumColumnWidth -eq "Auto") {
$MaximumColumnWidth = [Math]::Floor(($Host.UI.RawUI.BufferSize.Width - [int]$Columns) / [int]$Columns)
}
}
$MenuListing = @()
Function New-MenuListing($MenuOptions) {
If ([int]$Columns -gt $MenuOptions.count) {
$Columns = $MenuOptions.count
}
$RowQty = ([Math]::Ceiling(($MaxValue +1) / [int]$Columns))
# This loop is used to format the menu listing into a
# two-dimensional array, where each row is a column in the
# final menu listing. The outer loop iterates over the columns
# while the inner loop iterates over the rows of the column.
# The inner loop fetches the menu options for each column and
# then formats them to fit within the maximum column width.
# The formatted menu options are stored in the $MenuListing array.
For ($i=0; $i -lt $Columns; $i++) {
$ScratchArray = @()
For ($j=($RowQty*$i); $j -lt ($RowQty*($i+1)); $j++) {
$ScratchArray += $MenuOptions[$j]
}
$ColWidth = ($ScratchArray |Measure-Object -Maximum -Property length).Maximum
If ($ColWidth -gt [int]$MaximumColumnWidth) {
$ColWidth = [int]$MaximumColumnWidth-1
}
For ($j=0; $j -lt $ScratchArray.count; $j++) {
If (($ScratchArray[$j]).length -gt $([int]$MaximumColumnWidth -2)) {
$ScratchArray[$j] = $($ScratchArray[$j]).Substring(0,$([int]$MaximumColumnWidth-2))
$ScratchArray[$j] = "$($ScratchArray[$j])…"
}
Else {
For ($k=$ScratchArray[$j].length; $k -lt $ColWidth; $k++) {
$ScratchArray[$j] = "$($ScratchArray[$j]) "
}
}
$ScratchArray[$j] = " $($ScratchArray[$j]) "
}
$MenuListing += $ScratchArray
}
return $MenuListing, $Columns, $RowQty
}
Function New-TextBlock ($Block, $BlockName, $ForegroundColor = -1, $Selection, $MenuOptions, $MenuOptionsInput, $esc, $CL) {
$retBuffer = ""
if ($Block) {
if ($Block -is [String]) {
$retBuffer += ConvertTo-CreateMenuAnsiColorString $Block -ForegroundColor $ForegroundColor
}
else {
$retBuffer += Invoke-Command -ScriptBlock { param($Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $esc, $CL) # supply usable variables
# in module fix
try { $sbStr = Get-Variable -Name "Block" -Scope 1 -ValueOnly ; $Block = [scriptblock]::Create($sbStr) }
catch { }
Invoke-Command -ScriptBlock $Block *>&1 -OutVariable Lines | Out-Null
$retBuffer += ConvertTo-CreateMenuAnsiColorString $Lines -ForegroundColor $ForegroundColor # manual color
return $retBuffer
} -ArgumentList $Selection, $MenuOptions[$Selection], $MenuOptions, $MenuOptionsInput, $esc, $CL
}
}
return $retBuffer | ForEach-Object { "$CL$_" } #! ALWAYS clean the line before using
}
Function CleanHost {
$Host.UI.RawUI.CursorPosition = @{x=0; y=$Host.UI.RawUI.CursorPosition.Y - $bufferHeight}
Write-Host ("$CL`n" *( $bufferHeight)).Trim()
$Host.UI.RawUI.CursorPosition = @{x=0; y=$Host.UI.RawUI.CursorPosition.Y - $bufferHeight}
}
$MenuListing, $Columns, $RowQty = New-MenuListing $MenuOptions
[Console]::CursorVisible = $False
$bufferHeight = 0
$bufferHeightBackup = 0
$esc = [char]27
$CL = "$esc[0;0m$esc[0J"
$topBufferHeight = -1
if ($ClearHost) {
[Console]::Clear()
}
While ($True) {
$outputBuffer = ""
$bufferHeightBackup = $bufferHeight
If ($ShowCurrentSelection) {
$Host.UI.RawUI.WindowTitle = "CURRENT SELECTION: $($MenuOptions[$Selection])"
}
# set cursor back to beginning of output (top of screen has to be processed at least once)
if ($topBufferHeight -ge 0 -and ($DebugPreference -ne 'Continue') ) {
$Host.UI.RawUI.CursorPosition = @{x=0; y=$Host.UI.RawUI.CursorPosition.Y - $topBufferHeight}
}
# generate output
$outputBuffer += New-TextBlock $Title "title-block" $ForegroundColorTitle $Selection $MenuOptions $MenuOptionsInput $esc $CL
# output selections
For ($i=0; $i -lt $RowQty; $i++) {
For ($j=0; $j -le (($Columns-1)*$RowQty);$j+=$RowQty) {
$value = $MenuListing[$i+$j]
if ($null -ne $FilterCallback) {
$MenuOptions,$Selection,$CellValue = Invoke-Command -ScriptBlock { param($CellValue, $Current, $CurrentValue, $Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $esc, $CL, $Row, $Column)
# in module fix
try { $sbStr = Get-Variable -Name "FilterCallback" -Scope 1 -ValueOnly ; $FilterCallback = [scriptblock]::Create($sbStr) }
catch { }
$CellValue = . $FilterCallback
Return $MenuOptions, $Selection, $CellValue
} -ArgumentList $value, ($i+$j), $MenuOptions[$i+$j] , $Selection, $MenuOptions[$Selection], $MenuOptions, $MenuOptionsInput, $esc, $CL, $i, $j
$value = if ($CellValue) { $CellValue } else { $value }
}
# is end of line
If ($j -eq (($Columns-1)*$RowQty)) {
If (($i+$j) -eq $Selection){
$outputBuffer += ConvertTo-CreateMenuAnsiColorString "$($value)$CL`n" -BackgroundColor $BackgroundColorSelection -ForegroundColor $ForegroundColorSelection
} Else {
$outputBuffer += ConvertTo-CreateMenuAnsiColorString "$($value)`n" -ForegroundColor $ForegroundColor
}
# is in row
} Else {
If (($i+$j) -eq $Selection) {
$outputBuffer += ConvertTo-CreateMenuAnsiColorString "$($value)" -BackgroundColor $BackgroundColorSelection -ForegroundColor $ForegroundColorSelection
} Else {
$outputBuffer += ConvertTo-CreateMenuAnsiColorString "$($value)" -ForegroundColor $ForegroundColor
}
}
}
}
$outputBuffer += New-TextBlock $Footer "footer-block" $ForegroundColorFooter $Selection $MenuOptions $MenuOptionsInput $esc $CL
$outputBuffer += " " # prevents line jumping
# do positioning
$bufferHeight = ($outputBuffer | Measure-Object -Line).lines
$currentBufferHeight = $Host.UI.RawUI.BufferSize.Height
If ($bufferHeight -gt $currentBufferHeight) {
throw "Menu too tall to fit in buffer. Increase buffer height. (Window height)"
}
Write-Host $outputBuffer
$topBufferHeight = $bufferHeight # back to start line
if ($bufferHeight -lt $bufferHeightBackup) { # rows changed inbetween
Write-Host ("$CL`n" *( $bufferHeightBackup - $bufferHeight)).Trim()
$topBufferHeight = $bufferHeightBackup
}
# $Host.UI.RawUI.CursorPosition = @{x=0; y=$Host.UI.RawUI.CursorPosition.Y - $topBufferHeight}
if ($EnterPressed) {
If ($ClearHost) {
[Console]::Clear()
}
Break
}
$KeyInput = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").virtualkeycode
Switch ($KeyInput) {
13 { #Enter
# ignore empty fields
If ($MenuOptions[$Selection]) {
$EnterPressed = $True
# set title to before menu
If ($ShowCurrentSelection) {
$Host.UI.RawUI.WindowTitle = $WindowTitleBackup
}
If ($ClearHost) {
[Console]::Clear()
}
if ($CleanHost) {
CleanHost
}
$returnContent = $null
If ($ReturnObject) {
$items = @()
For ($i=0; $i -lt $MenuOptions.length; $i++) {
$items += @{
"Name" = $MenuOptions[$i];
"Index" = $i;
"Input" = $MenuOptionsInput[$i];
}
}
$returnContent = @{
"Selection" = $Selection;
"SelectionValue" = $MenuOptions[$Selection];
"MenuOptions" = $MenuOptions;
"MenuOptionsInput" = $MenuOptionsInput;
"Items" = $items;
}
}
ElseIf ($PassThrou) {
$returnContent = $MenuOptions[$Selection]
}
Else {
$returnContent = $Selection
}
# $Host.UI.RawUI.CursorPosition = @{x=0; y=$Host.UI.RawUI.CursorPosition.Y + $BufferHeight}
[Console]::CursorVisible = $true
Return $returnContent
}
Break
}
37 { #Left
If ($Selection -ge $RowQty){
$Selection -= $RowQty
} Else {
$Selection += ($Columns-1)*$RowQty
}
Break
}
38 { #Up
If ((($Selection+$RowQty)%$RowQty) -eq 0) {
# If the selection is at the start of a row, move to the last item in the row
$Selection += $RowQty - 1
} Else {
$Selection -= 1
}
Break
}
39{ #Right
# If the selection is at the end of a row, move to the previous row
# by subtracting the number of columns minus one multiplied by the row quantity
# Otherwise, move to the next item in the row
If ([Math]::Ceiling($Selection/$RowQty) -eq $Columns -or ($Selection/$RowQty)+1 -eq $Columns){
$Selection -= ($Columns-1)*$RowQty
} Else {
$Selection += $RowQty
}
Break
}
40 { #Down
If ((($Selection+1)%$RowQty) -eq 0 -or $Selection -eq $MaxValue){
$Selection = ([Math]::Floor(($Selection)/$RowQty))*$RowQty
} Else {
$Selection += 1
}
Break
}
Default {
}
}
if ($SelectionCallback -ne $Null) {
# return new MenuOptions, if you want
$ret,$sel,$res = Invoke-Command -ScriptBlock { param($Selection, $SelectionValue, $MenuOptions, $MenuOptionsInput, $KeyInput, $esc, $CL)
# in module fix, replace wiht localized script block
try { $sbStr = Get-Variable -Name "SelectionCallback" -Scope 1 -ValueOnly ; $SelectionCallback = [scriptblock]::Create($sbStr) }
catch { }
$res = . $SelectionCallback
Return $MenuOptions, $Selection, $res
} -ArgumentList $Selection, $MenuOptions[$Selection], $MenuOptions, $MenuOptionsInput, $KeyInput, $esc, $CL
If ($sel -is [Int]) {
$Selection = $sel
}
If ($res -eq $False) {
$EnterPressed = $True
# set title to before menu
If ($ShowCurrentSelection) {
$Host.UI.RawUI.WindowTitle = $WindowTitleBackup
}
}
ElseIf ($ret -is [Array]) {
$MenuOptions = $ret
$MenuListing, $Columns, $RowQty = New-MenuListing $MenuOptions
}
}
}
}
Function ConvertTo-CreateMenuAnsiColorString {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true, Position=0)]
[string]$InputString,
[Parameter(Mandatory=$false)]
[string]$ForegroundColor,
[Parameter(Mandatory=$false)]
[string]$BackgroundColor,
[Parameter(Mandatory=$false)]
[string[]]$Styles
)
$esc = [char]27 # using char 27 instead of string "`e" because not using `e makeis it PS 5.1 compatible
$ansiColors = @{
# Powershell defined ones, 0-15
Black = 0; DarkBlue = 4; DarkGreen = 2; DarkCyan = 6; DarkRed = 1; DarkMagenta = 5; DarkYellow = 3; Gray = 7; DarkGray = 8; Blue = 12; Green = 10; Cyan = 14; Red = 9; Magenta = 13; Yellow = 11; White = 15;
}
$fgColor = $ansiColors[$ForegroundColor]
$bgColor = $ansiColors[$BackgroundColor]
$ansiStyles = @{ Normal = 0; Bold = 1; Dim = 2; Italic = 3; Underline = 4; Blink = 5; RapidBlink = 6; Reverse = 7; Hidden = 8; }
if ($Styles) {
$styleSequence = $Styles | ForEach-Object { "$esc[{0}m" -f $ansiStyles[$_] }
}
if ("$fgColor" -and "$bgColor") {
return "$styleSequence$esc[38;5;{0};48;5;{1}m{2}$esc[0m" -f $fgColor, $bgColor, $InputString
} elseif ("$fgColor") {
return "$styleSequence$esc[38;5;{0}m{1}$esc[0m" -f $fgColor, $InputString
} elseif ("$bgColor") {
return "$styleSequence$esc[48;5;{0}m{1}$esc[0m" -f $bgColor, $InputString
} elseif ($Styles) {
return "$styleSequence$InputString$esc[0m"
} else {
return $InputString
}
}
Main
<#
BananaAcid 2022-2024
License MIT
v1.0.3
To run this file and get PowerShellGSM installed, paste the following into your powershell:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://gist.githubusercontent.com/bananaacid/1dc9117571967b26ceabc972009137ae/raw/install_PowerShellGSM.ps1'))
Changes:
- 16.02.2024 fix for PalWorld config: parsed the replacement block (Sart-ServerProp) as well
#>
Function Main() {
## Source file: https://github.com/patrix87/PowerShellGSM/archive/refs/tags/2.9.zip --> folder inside: PowerShellGSM-2.9.zip:.\PowerShellGSM-2.9\*.*
## -> what a horrible naming party / dynamic every time.
# $tag = "latest"
# $file = "random_name.zip"
# $url = "https://github.com/patrix87/PowerShellGSM/releases/download/$tag/$file"
$url = "https://github.com/patrix87/PowerShellGSM/archive/refs/heads/main.zip"
$tmpFile = "$env:TEMP\PowerShellGSM.main.zip";
# ---> Extracted filepath will always be PowerShellGSM-main !
$lastState = Get-Content ~\.PowerShellGSM -erroraction 'silentlycontinue' | ConvertFrom-Json -erroraction 'silentlycontinue'
if ( -not $? ) {
$lastState = [pscustomobject]@{
exPath = ""
}
}
else {
echo "Installation found in: $($lastState.exPath)"
}
## DOWNLOAD
if (Test-Path $tmpFile) {
$r = Read-Host "Download fresh PowerShellGSM files (Y/n)"
if ($r -ne "" -and $r -ne "y" -and $r -ne "Y") {
$noDl = $True
}
}
if ($noDl -ne $True) {
echo "downloading $url"
Invoke-WebRequest $url -OutFile $tmpFile
## EXTRACT
if ($lastState.exPath -ne "") { $def = "to " + $lastState.exPath } else { $def = "here" }
$r = Read-Host "Extract installation file $def (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
if ($lastState.exPath -ne "") { $r = $lastState.exPath } else { $r = pwd }
}
else {
$r = Read-Host "Installation path"
}
$exPath = $r
echo "Installation path used: $exPath"
Expand-Archive $tmpFile $env:TEMP -Force
ni $exPath -ItemType Directory -Force >$null
cp $env:TEMP\PowerShellGSM-main\* $exPath\ -Force -R
rm $env:TEMP\PowerShellGSM-main -R -Force
}
else {
if ($lastState.exPath -ne "") { $def = $lastState.exPath } else { $def = "here" }
$r = Read-Host "Enter path to previous installation (default: $def)"
if ($r -eq "") {
if ($lastState.exPath -ne "") { $r = $lastState.exPath } else { $r = pwd }
}
$exPath = $r
}
## ACTIVATE TEMPLATE
echo "Select a server template to add:"
$Options = ls $exPath\templates |? {$_.Name -notlike '#*'} |% BaseName
$Selection = Create-Menu -MenuOptions $Options -Columns "auto" -MaximumColumnWidth 20 -ShowCurrentSelection $True
$tmplFile = $Options[$Selection]
$cfgFile = "$tmplFile.psm1"
if (Test-Path $exPath\configs\$cfgFile) {
$r = Read-Host "Overwrite the config file for the selected server with new and blank one (backup will be created) (y/N)"
if ($r -eq "" -or $r -eq "n" -or $r -eq "N") {
$ignoreReplace = $True
}
else {
# just to make sure ...
$timestamp = Get-Date -format "yyyy-MM-dd_HH-mm-ss"
cp $exPath\configs\$cfgFile $exPath\configs\$cfgFile.backup.$timestamp -Force
cp $exPath\launchers\$tmplFile.cmd $exPath\launchers\$tmplFile.cmd.backup.$timestamp
}
}
if ($ignoreReplace -ne $True) {
cp $exPath\templates\$cfgFile $exPath\configs -Force
cp $exPath\launchers\run.cmd $exPath\launchers\$tmplFile.cmd -Force
}
echo "To run the selected server for $tmplFile, run $exPath\launchers\$tmplFile.cmd"
## change passwords
$r = Read-Host "Change all passwords in the new config (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
# get content
$inputFile = gc $exPath\configs\$cfgFile -Raw
# find description, key, value (password)
$results = [Regex]::Matches($inputFile, "^[\s]+(?<desc>#.*)*\n^[\s]+(?<line>(?<key>[^#'\s]*Password[^=\s]*)[\s]*=[\s]*""(?<val>[^""]*)"")", "IgnoreCase,Multiline")
$newFile = $inputFile
For ($i = 0; $i -lt $results.count; $i++) {
# ask for new password and show comment + old value
Write-Host "$($results[$i].Groups['desc'].Value) -- currently: '$($results[$i].Groups['val'].Value)'" -ForegroundColor DarkGray
$val = Read-Host "New password for '$($results[$i].Groups['key'].Value)' (empty for random)"
if ($val -eq "") {
$val = ((-join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 8 |%{[char]$_})) -replace '0|O|o|I|l|1|5|S|s','A').ToLower() # if not toLower, add: |Y|y|V|v|K|k|W|w|P|p|C|c|X|x
Write-Host "... random: $val"
}
# replace old value in line
$a = '"' + $results[$i].Groups["val"].Value + '"'
$b = '"' + $val + '"'
$x = $results[$i].Groups["line"].Value -replace $a,$b
# replace old row
$newFile = $newFile -replace $results[$i].Groups["line"].Value,$x
}
Write-Host ""
# write new content
$newFile | Out-File $exPath\configs\$cfgFile
}
## SHOW CONFIG
$r = Read-Host "Show config file to be edited manually (Y/n)"
if ($r -eq "" -or $r -eq "y" -or $r -eq "Y") {
notepad $exPath\configs\$cfgFile
explorer /select,"$exPath\configs\$cfgFile"
}
## REMEMBER
$lastState.exPath = Convert-Path $exPath
ConvertTo-Json $lastState | Out-File ~\.PowerShellGSM
}
## MODULES
# https://community.spiceworks.com/scripts/show/4785-create-menu-2-0-arrow-key-driven-powershell-menu-for-scripts
# Modified by BananaAcid
Function Create-Menu (){
#Start-Transcript "C:\_RRC\MenuLog.txt"
Param(
[Parameter(Mandatory=$True)][array]$MenuOptions,
[Parameter(Mandatory=$True)][String]$Columns = "Auto",
[Parameter(Mandatory=$False)][int]$MaximumColumnWidth=20,
[Parameter(Mandatory=$False)][bool]$ShowCurrentSelection=$False
)
$consoleTop = $False;
$MaxValue = $MenuOptions.count-1
$Selection = 0
$EnterPressed = $False
If ($Columns -eq "Auto"){
$WindowWidth = (Get-Host).UI.RawUI.MaxWindowSize.Width
$Columns = [Math]::Floor($WindowWidth/($MaximumColumnWidth+2))
}
If ([int]$Columns -gt $MenuOptions.count){
$Columns = $MenuOptions.count
}
$RowQty = ([Math]::Ceiling(($MaxValue+1)/$Columns))
Function ClearHost () {
if ($consoleTop -eq $False) {
$consoleTop = [Console]::CursorTop - $RowQty
}
[Console]::SetCursorPosition(0, $consoleTop)
}
$MenuListing = @()
For($i=0; $i -lt $Columns; $i++){
$ScratchArray = @()
For($j=($RowQty*$i); $j -lt ($RowQty*($i+1)); $j++){
$ScratchArray += $MenuOptions[$j]
}
$ColWidth = ($ScratchArray |Measure-Object -Maximum -Property length).Maximum
If ($ColWidth -gt $MaximumColumnWidth){
$ColWidth = $MaximumColumnWidth-1
}
For($j=0; $j -lt $ScratchArray.count; $j++){
If(($ScratchArray[$j]).length -gt $($MaximumColumnWidth -2)){
$ScratchArray[$j] = $($ScratchArray[$j]).Substring(0,$($MaximumColumnWidth-4))
$ScratchArray[$j] = "$($ScratchArray[$j])..."
} Else {
For ($k=$ScratchArray[$j].length; $k -lt $ColWidth; $k++){
$ScratchArray[$j] = "$($ScratchArray[$j]) "
}
}
$ScratchArray[$j] = " $($ScratchArray[$j]) "
}
$MenuListing += $ScratchArray
}
While($EnterPressed -eq $False){
If ($ShowCurrentSelection -eq $True){
$Host.UI.RawUI.WindowTitle = "CURRENT SELECTION: $($MenuOptions[$Selection])"
}
For ($i=0; $i -lt $RowQty; $i++){
For($j=0; $j -le (($Columns-1)*$RowQty);$j+=$RowQty){
If($j -eq (($Columns-1)*$RowQty)){
If(($i+$j) -eq $Selection){
Write-Host -BackgroundColor cyan -ForegroundColor Black "$($MenuListing[$i+$j])"
} Else {
Write-Host "$($MenuListing[$i+$j])"
}
} Else {
If(($i+$j) -eq $Selection){
Write-Host -BackgroundColor Cyan -ForegroundColor Black "$($MenuListing[$i+$j])" -NoNewline
} Else {
Write-Host "$($MenuListing[$i+$j])" -NoNewline
}
}
}
}
#Uncomment the below line if you need to do live debugging of the current index selection. It will put it in green below the selection listing.
#Write-Host -ForegroundColor Green "$Selection"
$KeyInput = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown").virtualkeycode
Switch($KeyInput){
13{
$EnterPressed = $True
Return $Selection
ClearHost
break
}
37{ #Left
If ($Selection -ge $RowQty){
$Selection -= $RowQty
} Else {
$Selection += ($Columns-1)*$RowQty
}
ClearHost
break
}
38{ #Up
If ((($Selection+$RowQty)%$RowQty) -eq 0){
$Selection += $RowQty - 1
} Else {
$Selection -= 1
}
ClearHost
break
}
39{ #Right
If ([Math]::Ceiling($Selection/$RowQty) -eq $Columns -or ($Selection/$RowQty)+1 -eq $Columns){
$Selection -= ($Columns-1)*$RowQty
} Else {
$Selection += $RowQty
}
ClearHost
break
}
40{ #Down
If ((($Selection+1)%$RowQty) -eq 0 -or $Selection -eq $MaxValue){
$Selection = ([Math]::Floor(($Selection)/$RowQty))*$RowQty
} Else {
$Selection += 1
}
ClearHost
break
}
Default{
ClearHost
}
}
}
}
Main
@KngJn

KngJn commented Aug 3, 2023

Copy link
Copy Markdown

throws a bunch of errors on my side and leaves me with a blank .ps1

@BananaAcid

Copy link
Copy Markdown
Author

throws a bunch of errors on my side and leaves me with a blank .ps1

In case you get read/write/access/permission errors, create a folder like c:\server-games and run the installation command within that directory.

@spydey20

spydey20 commented Apr 13, 2024

Copy link
Copy Markdown

Any chance this could support server clustering in Ark Ascended?

Thank you for this by the way! Works great.

@BananaAcid

Copy link
Copy Markdown
Author

Any chance this could support server clustering in Ark Ascended?

Thank you for this by the way! Works great.

I don't know what is required for clustering, but PowershellGSM allows running multiple servers. If it relies on multiple instances, you might just need to duplicate the configs in PowershellGSM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment