Convert cmd scripts to PowerShell
I recently moved many scripts from cmd (.bat
or .cmd
) to PowerShell (.ps1
). In this post, I'll show all the differences I've seen during the migration. This is list is not exhaustive as it only contains the cases I had encountered in the migrated scripts.
#Why using PowerShell instead of cmd?
PowerShell has many advantages compared to cmd:
- Discoverability: PowerShell makes it easy to discover its features thanks to the auto-complete, the command
Get-Command
andGet-Help
. - Consistency: The consistency of PowerShell is one of its primary assets. For example, if you learn how to use the
Sort-Object
cmdlet, you can use that knowledge to sort the output of any cmdlet. You don't have to learn the different sorting routines of each cmdlet. - Interactive and scripting environments: PowerShell combines an interactive shell and a scripting environment. PowerShell can access command-line tools, COM objects, and .NET class libraries.
- Object orientation: PowerShell is based on object, not text. The output of a command is an object. You can send the output object, through the pipeline, to another command as its input.
- Cross-platform: PowerShell runs on Windows, Linux, and Mac.
- Testable using Pester
If you still have a doubt about using PowerShell instead of cmd, Rich Turner explains clearly that PowerShell is the future:
Cmd is in maintenance mode. It's job is to preserve back compat with ancient & immutable scripts. That's it. Period. It should not be used for interactive shell work.
PowerShell is the future.
Rich Turner, Sr. Program Manager, Windows Console & Command-Line
#There are 2 flavors of PowerShell
There are 2 flavors of PowerShell:
- PowerShell (
powershell.exe
) is a component of Windows. This version works only on Windows and won't receive any new features. - PowerShell Core (
pwsh.exe
) is available on GitHub. This flavor works on Windows, Linux, and Mac. This is the version Microsoft works on.
If you have the choice, go with the latest version of pwsh
. You can find it on GitHub: Download PowerShell.
#cmd/PowerShell differences
##Expand environment variables
echo %test%
echo $env:test
If the variable name has complex characters you need to use curly brackets (${env:name}
)
echo %ProgramFiles(x86)%
echo ${env:ProgramFiles(x86)}
The behavior is different when you use the environment variable as an argument. cmd expand the environment variables and then execute the command line. So, you need to escape arguments. PowerShell consider it as a single argument and escape it correctly without any additional work.
$test="a b"
sample.exe $test # 1 argument
$test=@("a", "b") # Declare an array
sample.exe $test # 2 arguments
##Set environment variable
set test=abc
$env:test="abc"
Set-Item -Path Env:test -Value "abc"
set /p test=<MyFilename.txt
$env:test=Get-Content MyFilename.txt -First 1 # Read only the first line to match the cmd behavior
You can set the environment variables for the current user or machine using C#:
# Get the value
$path = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') # User or Machine
# Set the value
[Environment]::SetEnvironmentVariable("PSModulePath", $newpath, 'Machine')
Note that if the variable is used only in the script, you can use local variables instead of environment variables
$test="abc" # the variable is only valid in the current script
echo $test
##List environment variables
set
Get-ChildItem env:
##Get current directory
echo %cd%
echo $pwd
##Get script directory
echo %~dp0
echo $PSScriptRoot
##Get exit code
ping 127.0.0.1
echo %errorlevel%
echo $lastexitcode
If the previous command is a cmdlet, you can use $?
to know if the command succeeds. $?
returns a boolean value.
PS> Get-ChildItem
PS> echo $?
True
Documentation:
##Execute executable in the current directory
To execute a file that is in the current directory, you need to prefix the name by .\
myexe.exe
.\myexe.exe
##echo has a different behavior
In cmd, echo
prints the argument as is. In PowerShell, echo
is an alias for Write-Output
which prints each argument on a line.
$> echo a b c
a b c
PS> echo a b c
a
b
c
PS> echo "a b c"
a b c
PS> echo a b c | Join-String -Separator " "
a b c
Documentation:
##Redirecting output to a file
The syntax is the same, you can use command > filename
except there must be a space before the >
ping 127.0.0.1> output.txt
ping 127.0.0.1 > output.txt # There must be a space before ">"
ping 127.0.0.1 >> output.txt
ping 127.0.0.1 >> output.txt # There must be a space before ">>"
You can also use the Out-File
cmdlet which provides more options such as file encoding:
ping 127.0.0.1 > output.txt
ping 127.0.0.1 | Out-File output.txt
ping 127.0.0.1 >> output.txt
ping 127.0.0.1 | Out-File output.txt -Append
Documentation:
##Escaping characters
In cmd, you can prefix a character by ^
to escape it. For instance, you can insert a new line by finishing a line by ^
. In PowerShell, you can prefix any character by `
to escape it. Escaping character allows to:
Remove the special meaning of the character
PowerShellPS> $def="variable" PS> Write-Host "Value of `$def: $def" Value of $def: variable PS> Write-Host abc `| def abc | def PS> Write-Host "abc`"def" abc"def PS> Write-Host "abc``def" abc`def
To represent special character
`0
: null`a
: Alert`b
: Backspace`e
: Escape`f
: Form feed`n
: New line`r
: Carriage return`t
: Horizontal tab`v
: Vertical tab`u{x}
: Unicode escape sequence (`u{2195}
represent the up down arrow (↕) symbol)
Documentation:
##Quoting arguments
The quoting rules are not the same in cmd and PowerShell. Mainly because PowerShell has the notion of expression and a special behavior for arguments starting with a dash -
. In argument mode, each value is treated as an expandable string unless it begins with one of the following special characters: dollar sign ($
), at sign (@
), single quotation mark ('
), double quotation mark ("
), or an opening parenthesis ((
). In these cases, the value is treated as an expression and is evaluated.
PS> echo 2+2
2+2
PS> echo (2+2)
4
PS> echo "(2+2)"
(2+2)
PS> $a = 2+2
PS> echo $a
4
Arguments starting with a dash have special parsing rules, so you sometimes need to escape them:
PS> echo arg=abc.txt # 1 argument
arg=abc.txt
PS> echo -arg=abc.txt # 2 arguments
-arg=abc
.txt
PS> echo "-arg=abc.txt"
-arg=abc.txt
The parameter --
is a special parameter that indicates to not bind parameters after this.
PS> Write-Output -InputObject
Write-Output: Missing an argument for parameter 'InputObject'. Specify a parameter of type 'System.Management.Automation.PSObject' and try again.
PS> Write-Output -- -InputObject
-InputObject
The stop-parsing symbol (--%
) directs PowerShell to refrain from interpreting input as PowerShell commands or expressions. When it encounters a stop-parsing symbol, PowerShell treats the remaining characters in the line as a literal. The only interpretation it performs is to substitute values for environment variables that use standard Windows notation, such as %USERPROFILE%
. The stop-parsing symbol is effective only until the next newline or pipeline character. For example, the 2 following commands are equivalents:
PS> icacls X:\VMS /grant Dom\HVAdmin:`(CI`)`(OI`)F
PS> icacls X:\VMS --% /grant Dom\HVAdmin:(CI)(OI)F
Documentation:
##call command
The call operator lets you run commands that are stored in variables and represented by strings or script blocks.
call %exe%
& $env:exe
It is also necessary if the command must be quoted:
PS> & ".\script name with spaces.ps1"
Documentation:
##call a bat script
You can call bat script using cmd.exe
:
myscript.bat
cmd.exe /c myscript.bat
folder/myscript.bat
cmd.exe /c folder\myscript.bat # must use backslash on Windows
Note that the environment variables set in the bat script won't be available in the PowerShell host after the script is executed.
##if block
set a=0
if %a% == 0 (
echo true
) else (
echo false
)
$a=0
if ($a -eq 0)
{
echo true
}
else
{
echo false
}
You can replace if defined VARIABLE
with the Test-Path
cmdlet:
if defined TEST (action) else (action)
if (Test-Path env:TEST) { action } else { action }
if not defined TEST (action)
if (-not (Test-Path env:TEST)) { action }
Documentation:
##Combining commands using && and ||
These 2 operators allow combining commands. They are supported starting with PowerShell 7. If you use an older version, you need to replace them with if
statements.
# Test-Command '1' || Test-Command '2'
Test-Command '1'; if (-not $?) { Test-Command '2' }
# Test-Command '1' && Test-Command '2'
Test-Command '1'; if ($?) { Test-Command '2' }
Documentation:
##String manipulation
You can use any .NET functions to manipulate strings. So, you can substring, replace, etc. using System.String.*
methods.
echo %var:~0,3%
echo $var.Substring(0, 3)
echo %var:~-3%
echo $var.Substring($var.Length - 3)
set test=123meziantou
echo %test:123=Hello %
$test="123meziantou"
echo $test.Replace("123", "Hello ")
Documentation:
##Wait for a few seconds
In cmd, it is common to use ping 127.0.0.1
to wait for a few seconds. In PowerShell, you can use the Sleep
cmdlet:
Start-Sleep -Seconds 5
##Test if a machine is accessible
You can validate that a remote machine is accessible before doing an action using the Test-Connection
cmdlet:
if (Test-Connection -TargetName Server01 -Quiet)
{
New-PSSession -ComputerName Server01
}
##Editing registry keys
You can use the Get-ItemProperty
and Set-ItemProperty
cmdlets to read and write registry keys:
# List values
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\"
# List values recursively
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\" -Recurse
# Read value
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName
# Create registry key
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft" -Name "Edge" -Force
# Create registry value
New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Edge -Name "BuiltInDnsClientEnabled" -Value "Yes" -Force
#Replace exe by cmdlet
You should replace Windows-specific executables with cross-platform and more readable cmdlets when possible.
rmdir /s /q C:\MyFolder
→Remove-Item C:\MyFolder -Recurse -Force
del /s /q file.txt
→Remove-Item file.txt -Recurse -Force
sc
→Get-Service
/Start-Service
/Stop-Service
/New-Service
/Remove-Service
/ etc.taskkill
→Stop-Process
xcopy
→Copy-Item
If you are curious enough, the following cmdlets are the only thing you need to learn PowerShell😉
Get-Help
Get-Command
Get-Member
Do you have a question or a suggestion about this post? Contact me!