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 and Windows Server 2008 R2. PowerShell must be installed on any other 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 you can deploy the .NET framework and PowerShell to all computers. Even then you need to be aware of issue 2 below.

The new Active Directory cmdlets, like Get-ADUser, require that you have at least one Domain Controller running Windows Server 2008 R2.

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 a 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 and Windows Server 2008R2 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.