Convert cmd scripts to PowerShell

  • Gérald Barré

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 and Get-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
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

Get-ChildItem env:

##Get current directory

echo %cd%
echo $pwd

##Get script directory

echo %~dp0
echo $PSScriptRoot

##Get exit code

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 $?


##Execute executable in the current directory

To execute a file that is in the current directory, you need to prefix the name by .\


##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

PS> echo "a b c"
a b c

PS> echo a b c | Join-String -Separator " "
a b c


##Redirecting output to a file

The syntax is the same, you can use command > filename except there must be a space before the >

ping> output.txt
ping > output.txt # There must be a space before ">"

ping >> output.txt
ping >> 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 > output.txt
ping | Out-File output.txt

ping >> output.txt
ping | Out-File output.txt -Append


##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

    PS> $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"
    PS> Write-Host "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)


##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

PS> echo (2+2)

PS> echo "(2+2)"

PS> $a = 2+2
PS> echo $a

Arguments starting with a dash have special parsing rules, so you sometimes need to escape them:

PS> echo arg=abc.txt # 1 argument

PS> echo -arg=abc.txt # 2 arguments

PS> echo "-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

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


##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"


##call a bat script

You can call bat script using cmd.exe:

cmd.exe /c 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
if ($a -eq 0)
    echo true
    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 }


##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' }


##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 %
echo $test.Replace("123", "Hello ")


##Wait for a few seconds

In cmd, it is common to use ping 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:\MyFolderRemove-Item C:\MyFolder -Recurse -Force
  • del /s /q file.txtRemove-Item file.txt -Recurse -Force
  • scGet-Service / Start-Service / Stop-Service / New-Service / Remove-Service / etc.
  • taskkillStop-Process
  • xcopyCopy-Item

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub