Issues to be aware of when transitioning from VBScript to PowerShell
Any new language requires some effort for you to get up to speed. If you have
experience with VBScript programs, being aware of the issues on this page can make
the transition easier. This list is based on my experience when I
first coded PowerShell scripts and expected things to work like they do when
I code VBScript programs. The list certainly is not exhaustive, but it can help
if your experience is similar to mine.
1. Requirements to Install PowerShell
PowerShell v2 comes installed on Windows 7, Windows Server 2008 R2, and all later versions.
PowerShell
must be installed on any older clients. PowerShell v1 or v2 can be installed on
Windows Server 2003, Windows XP SP2, Windows Vista, and Windows Server 2008.
Both versions of PowerShell require .NET 2.0 Framework, so many of these OS's will require
upgrade from .NET 1.0. Some features of PowerShell v2 require .NET 3.5, which
comes with Windows 7 and Windows Server 2008 R2, but may require an upgrade
on the other OS's. These issues make PowerShell a poor choice for logon
scripts, unless all of your clients have Windows 7 or above, or you can deploy the
.NET framework and PowerShell to all computers. Even then you need to be
aware of issue 2 below.
The Active Directory cmdlets, like Get-ADUser, require that you have at
least one Domain Controller running Windows Server 2008 R2 or higher.
2. Execution Policy
The default execution policy is "Restricted", which means that you cannot
run scripts. You can only work interactively at the PowerShell prompt. If
you intend to run scripts, you must assign an execution policy that allows
it. Your choices are:
ExecutionPolicy | Description |
Restricted | Scripts are not allowed to run, profile scripts cannot load (the default). |
AllSigned | All scripts and profiles must be digitally signed (with a code-signing certificate from a trusted certificate authority). |
RemoteSigned | All scripts and profiles downloaded from the Internet must be digitally signed. All others can be executed. |
UnRestricted | All scripts and profiles can be executed. |
You can run the following commands at the PowerShell prompt to check
the execution policy, and then to set the policy:
PS> Get-ExecutionPolicy
Restricted
PS> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
3. Expression Mode vs. Argument Mode
PowerShell commands and statements are interpreted differently depending on whether the
parser is in
expression mode or argument mode. Certain characters determine the mode. In
expression mode character strings must be enclosed in quotation marks, while
numbers are not. In argument mode, each value is treated as a string that
can be expanded, unless it begins with one of the following special
characters: a dollar sign ($), the at sign
(@), a single quotation mark
('), a double quotation mark
("), or an open parenthesis
"(". These
characters cause the value to be treated as an expression. Some examples
should explain:
Example | Mode | Result |
2+2 | Expression | 4 |
Write-Output 2+2 | Argument | "2+2" |
Write-Output (2+2) | Expression | 4 |
Get-Date.DayOfYear | Argument | <Error Message> |
(Get-Date).DayOfYear | Expression | 8 |
4. Variables
All variable names in PowerShell begin with the "$" character. For example
the following assigns the value "This is a test" to the variable $Value:
$Value = "This is a test"
5. Strings
Hard coded strings can be enclosed in either double or single quotes. The
difference is how embedded variables are resolved. If the string is enclosed
in double quotes, all variables are resolved (replaced with the value
assigned to the variable). If the string is enclosed in single quotes,
variables are not resolved. For example:
$Value = "Test"
$Line1 = "This is a $Value"
$Line2 = 'This is a $Value'
Write-Host $Line1
Write-Host $Line2
Results in the following:
This is a Test
This is a $Value
Although variables are resolved in double quoted strings, the properties of variables are not. For example:
$String = "This is a test"
Write-Host "Length: $String.Length"
$Length = $String.Length
Write-Host
"Length: $Length"
Results in the following:
Length: This is a test.Length
Length: 14
6. String Concatenation
String concatenation is handled differently in
PowerShell. The plus character, "+",
is the concatenation operator. However, the following PowerShell script does
not work as expected, because the Write-Host statement interprets the
strings in argument mode:
$Value = "Test"
$Line = "This is a " + $Value
Write-Host "Line = " + $Line
The result is:
Line = + This is a Test
The correct way to construct the string in the Write-Host statement is as follows:
$Value = "Test"
$Line = "This is a " + $Value
Write-Host "Line = $Line"
Which results in:
Line = This is a Test
However, the behavior is different if you put the value on the pipeline, instead of using Write-Host. For example, this code interprets the strings in expression mode:
$Value = "Test"
"This is a " + $Value
Which results in:
This is a Test
7. The "=" Character is Only an Assignment Operator
In VBScript the "=" character serves two functions.
It can be an assignment operator in a statement where a value is assigned to
a variable. For example:
strValue = 7
The "=" character is also an equality operator in VBScript. For example, we can test if two expressions are equal in an If statement, such as the following:
If (strValue = 7) Then
However, in PowerShell the "=" character is only used to assign values to variables. The equality operator in PowerShell is "-eq". For example, a PowerShell statement to check if strValue is equal to 7 would be:
If (strValue -eq 7)
If you mistakenly use the "=" character in the above, no error will be raised, but the script won't work as you expected. The other comparison operators include -ne, -gt, -ge, -lt, -le. There are others as well.
8. Characters That Must Be Escaped
The dollar sign "$" and backtick
"`" characters must be escaped with the backtick
PowerShell escape character if they appear in strings
quoted with double quotes. In addition, if a
string is quoted with double quote characters, then any double quote
characters in the string must be escaped with the backtick. Alternatively,
the embedded double quote characters can be doubled within the string. If a
string is quoted with single quote characters, then
"`" and "$" characters are not
resolved, but taken literally. You cannot escape characters with the
backtick in a single quoted string. If a single quoted string contains any
single quote
characters, double the single quote character. The
"$" character does not need to be escaped in a string quoted with
single quotes. A more complete
discussion of escaping characters in PowerShell, especially if Active
Directory Distinguished Names are hard coded, is found here:
PowerShellEscape.htm
9. Functions Do Not Return Values
Running a function may result in values being displayed, or values being
assigned to variables, or values passed on the pipeline, but the function itself is not assigned a value,
unless you use the Return statement in the function. Check
"Get-Help about_Return" for details.
10. A "Return" Statement Returns Everything Output by a Function
When a Return statement is encountered in a function, the function is
assigned the value specified in the Return statement, but also all previous
values output by the function. For example, a statement that outputs values
has been added to the following function for troubleshooting. You
might expect the function to return with either $True or $False, but it does
not.
Function IsGreater($x, $y)
{
"$x, $y"
If ($x -gt $y)
{
Return $True
}
Else
{
Return $False
}
}
If (IsGreater 5 7)
{
"5 is greater than 7"
" The function returns " + (IsGreater 5 7)
}
When you run the above example, you find the output will be as follows:
5 is greater than 7
The function returns: 5, 7 False
The result is always evaluated as $True. $False is really 0, and anything not 0 is $True. The string "5, 7 False" is $True in the If statement, because it is not $False. If the first statement in the above function is removed (where the values of the parameters are output), then it will behave as expected.
11. Functions Must Be Defined Before They Are Used
In PowerShell scripts the statements that define a function must appear
before the function is called. In VBScript the function definition could
appear anywhere.
12. Functions are Called with Parameters Not in Parentheses and Not
Separated by Commas
For example, consider the following function called MyFunction that takes two parameters:
Function MyFunction ($x, $y)
{
Write-Host "x = $x"
Write-Host "y = $y"
}
If you call the function as follows:
MyFunction 3, 21
The result will be:
x = 3 21
y =
This assumes that StrictMode has not been set, so syntax errors like this are ignored (see issue 14 below). The correct way to call the function is as follows:
MyFunction 3 21
Now the result will be:
x = 3
y = 21
13. PowerShell Scripts Generally Do Not Run When You Double Click
the File
The default behavior when you double click a *.ps1 file is to open the file
in notepad. In Windows 7, Windows Server 2008R2, and above the file is opened in the
PowerShell ISE.
14. How to Run PowerShell Scripts
At a PowerShell command prompt, you can navigate to the folder where the
PowerShell script is saved, using standard commands like cd.
If the script is called MyScript.ps1, you can execute it with the
following:
.\MyScript.ps1
You can also run a PowerShell script from a command console or the Run line with a statement similar to:
PowerShell c:\Scripts\MyScript.ps1
This works because PowerShell.exe is on the path. If the PowerShell script is not in the current directory, you must specify the full path to the file, as above. If the script is in the current directory, you can "dot source" the file, similar to:
PowerShell .\MyScript.ps1
The "." indicates the current directory.
15. By Default Scripts Do Not Halt on Errors
Especially if a script is looping through a large collection of objects,
like all the users in your domain, an error can cause a seemingly endless
stream of error messages. By default PowerShell outputs a message for any
errors raised, but continues to run the script. In some cases this behavior
can even be dangerous, or at least produce confusing results. I use a Trap
statement to change this behavior, so the script halts when the first error
is raised. This makes it easier for me to troubleshoot. The statement I use is:
Trap {"Error: $_"; Break;}
16. StrictMode is Not the Default
Some syntax errors produce unexpected results in PowerShell but do not raise an error
unless you set StrictMode. At the start of a PowerShell script you can use
the following command:
Set-StrictMode -Version latest
The Version parameter can be assigned the values 1.0, 2.0 or latest. However, Set-StrictMode and Get-StrictMode are not supported in PowerShell V1. For details check the help by entering the following at a PowerShell prompt:
Get-Help Set-StrictMode -detailed
17. ADSI Requires that / Characters be Escaped with the \ Escape Character
If you use a PowerShell script to
retrieve the Distinguished Name of an object, and then use this value to
create a DirectoryEntry object (bind to the object in Active Directory), be
aware that any "/" characters in
the Distinguished Name must be escaped
with the backslash escape character. All other characters that require
escaping in Active Directory, such as the comma, will be properly escaped
when you retrieve the value. This situation will be rare, but could cause
unexpected results that are difficult to troubleshoot. The following
PowerShell script demonstrates how to deal with this:
# Specify sAMAccountName of user.
# The Distinguished Name (DN) of this user is:
# "cn=Windows 2000/XP,ou=West,dc=MyDomain,dc=com".
$strName = "Win2kXP"
# Use DirectorySearcher to find the DN of the user from the sAMAccountName.
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Root = $Domain.GetDirectoryEntry()
$Searcher = [System.DirectoryServices.DirectorySearcher]$Root
$Searcher.Filter = "(sAMAccountName=$strName)"
$colResults = $Searcher.FindAll()
ForEach ($User In $colResults)
{
# The DN must be cast as String so we can use the Replace method.
$strDN = [String]$User.properties.Item("distinguishedName")
Write-Host $strDN
# This fails ($Name1 is missing), but no error is raised.
$User1 = [ADSI]"LDAP://$strDN"
$Name1 = $User1.Name
Write-Host "Name1 = $Name1"
# This works, because we escape the forward slash.
$strDN = $strDN.Replace("/", "\/")
$User2 = [ADSI]"LDAP://$strDN"
$Name2 = $User2.Name
Write-Host "Name2 = $Name2"
}
If your PowerShell script is retrieving the Distinguished Names of all users in a domain, for example, and then using these values to create Directory Entry objects, you really should go to the extra effort to escape any possible forward slash characters, unless you are certain there are none. This is not necessary if you use the AD cmdlets, like Get-ADUser, to bind to the objects in Active Directory. The AD cmdlets use the .NET framework, rather than ADSI. It really is ADSI, and not Active Directory, that requires that forward slash characters be escaped.
18. The Properties Method of an [ADSISearcher] SearchResult Requires
Attributes in all Lower Case
The following PowerShell script only works if the attribute names are
specified in all lower case:
$Searcher = [ADSIsearcher]"(&(objectCategory=person)(objectClass=user))"
$Searcher.Searchroot = "LDAP://ou=Staff,ou=West,dc=MyDomain,dc=com"
$Results = $searcher.findAll()
ForEach ($Result In $Results)
{
$Name = $Result.Properties.samaccountname
$First = $Result.Properties.givenname
"$Name ($First)"
}
The attribute values will not be retrieved if you use any other case, including the case used in the Active Directory Schema. You must use the LDAPDisplayName's of the attributes in all lower case. If you use any other case, such as givenName, no errors will be raised, but the value will be blank. You can avoid this if you use the Item method of the Properties collection. Then you can use any case. For example, the following PowerShell script works fine:
$Searcher = [ADSIsearcher]"(&(objectCategory=person)(objectClass=user))"
$Searcher.Searchroot = "LDAP://ou=Staff,ou=West,dc=MyDomain,dc=com"
$Results = $searcher.findAll()
ForEach ($Result In $Results)
{
$Name = $Result.Properties.Item("sAMAccOUntnAme")
$First = $Result.Properties.Item("gIVeNnaMe")
"$Name ($First)"
}
This is the only situation I've encountered where attribute names are case sensitive. I personally prefer the case used in the Active Directory schema, such as sAMAccountName and givenName. For this reason, I always use the Item method of the Properties collection.