SUPPORT@PowerShell.com

Shell Tools Support for Powershell Analyzer and Powershell Plus
Welcome to SUPPORT@PowerShell.com Sign in | Join | Help
in
Home Main Site Blogs Forums Videos Chat Customer Support

Mastering PowerShell in your Lunch Break

Day 3: Discovering objects (COM, WMI & ADSI)

Today for the last time we'll talk about old scripting techniques and how to port them to PowerShell.

That's because today you will learn all you possibly need to reuse old script code including a cool VBScript integrator called new-vbscript that let's you merge arbitrary VBScript/JScript/PerlScript code into PowerShell.

Stop. What if I don't care about VBScript?

We'll be talking a lot about ignorance in todays session, and ignorance goes both ways. One main objective for today is to show you that not ignoring each other (PowerShell and VBScript/old scripting languages) and instead working together as a team rewards incredible benefits for both sides.

So in the first half I'll be talking about discovering the new builtin PowerShell ways of doing stuff. As a VBScripter, you'll see that while you can hold on to the traditional object library way of doing things, you are better off (and faster at home) by looking at the new PowerShell capabilities. 

In the second half, I'll be talking about integration of old VBScript code into PowerShell - not because of sentimental reasons but simply because there are still areas where VBScript does better than PowerShell. PowerShell is fun but not (yet) the holy grail.

Translating VBScript to PowerShell...

Let's start with VBScript atoms. If you ever wondered how to translate a VBScript's Abs or Mid or FormatNumber statements to PowerShell, there's now a detailed guide for you:

http://www.microsoft.com/technet/scriptcenter/topics/winpsh/convert/default.mspx

This guide won't help you with the tricky stuff, though. Most of the magic in VBScript is done by external objects and involves COM, ADSI and WMI. Let's check out what that means and how you go about translating stuff like that to PowerShell.

The following script accesses the local file system to find out the modification date of a file. How would you port it to PowerShell?

Set fs = CreateObject("Scripting.FileSystemObject")

Set file = fs.GetFile("c:\windows\explorer.exe")

WScript.Echo file.DateLastAccessed

That’s easy with the knowledge of Day 2. The most important thing you need to know is how to replace CreateObject: new-object -comobject.

$fs = new-object -comobject Scripting.FileSystemObject

$file = $fs.GetFile("C:\windows\explorer.exe")

$file.DateLastAccessed

But: While this is a perfect working example, it is completely ignorant. And that's fine. You don't need to be politically correct. Still, your translation has completely ignored the fact that PowerShell offers new ways of accessing files and folders, and these built-in ways are much simpler that handing over resposibility to some external Scripting.FileSystemObject object library. 

Being ignorant is ok as long as it does not embarrass you. So before you present your translation proudly to friends and relatives at their next birthday party, take a look at how PowerShell accesses files natively:

Dir: FileSystem-Access Made Simple

You know Dir, Dir is your friend from old days, it lists the contents of a folder, and it is very easy to use. The important point to realize is that Dir – as any commandlet – does not return simple text information but instead real objects. So Dir in fact returns the file objects. Just like this:

$file = Dir c:\windows\explorer.exe

$file.LastAccessTime

Wew... this approach looks a lot smarter (and shorter and easier and more readable) than your initial translation. Ok, it may pay off to learn something new...

So the next time you want to do something and think hard which particular external library would be responsible for that - hold on. Wait a minute. Get you a coffee and see if there is no internal PowerShell way of doing it. If what you want to do is not completely exotic and out-of-the-way, chances are it is built into PowerShell in the first place.

Environment Variables Without the Overhead

Hungry for more? Let's take environment variables. They are important, and it took a lot of code and yet another external library to read the content of such an environment variable. The next VBScript for example looks for explorer.exe in the folder specified by %windir% and needs to hire the additional expert capabilities of WScript.Shell:

Set fs = CreateObject("Scripting.FileSystemObject")

Set shell = CreateObject("WScript.Shell")

windir = shell.Environment("Process")("Windir")

Set file = fs.GetFile(windir & "\explorer.exe")

WScript.Echo file.DateLastAccessed

Now here's the PowerShell way of doing that: 

$file = Dir $env:windir\explorer.exe

$file.LastAccessTime

Huh? Let’s examine this line by line. In the first line, you use Dir to access a specific file and store the result – the actual file object – in a variable called $file.

Instead of hard coding the path to your windows folder, you insert the content of the %windir% environment variable. In Day 1, you have seen how PowerShell organizes all kinds of information as drives, and the environment variables are accessible in the virtual env: drive. By preceding this with a “$”, you access its content.

Now, wouldn’t you have expected something like this?

Dir $env:windir + "\explorer.exe"

PowerShell works a bit different. Dir would take only the first part of your path ($env:windir) and wouldn’t know what to do with  the rest. If you want to add together stuff and use that as an argument, you would have to enclose it into parenthesis to point out that you want it evaluated before it is used by anyone else:

Dir ($env:windir + "\explorer.exe")

This would work but is considerably more effort than the original line. The secret is: You can insert any variable into your strings, and the variable content is automagically merged into the string IF and only if the text is either not enclosed in quotes at all or enclosed in double quotes. Single quotes are used when you don’t want this kind of magic:

"Windir: $env:windir"

'Windir: $env:windir'

In the remaining line, since $file contains the file object, you can access any of its properties and methods. Right away. However, since we are no longer using the awkward object model of Scripting.FileSystemObject but instead the more standardized .NET object model, the property we are after is no longer called DateLastAccessed but instead LastAccessTime.

How do you know? Either, you use one of our specialized PowerShell editors like PowerShellAnalyzer or PowerShellIDE which give you Intellisense-like support. Or you ask for a list of available properties and methods:

$file = Dir $env:windir\explorer.exe

$file | get-member

Now, PowerShell dumps out all the necessary information. In the first column, you see the methods’ and properties’ names. In the second column, you read whether it is a method, a property, a … wait a minute! … NoteProperty or a ScriptProperty. There are obviously a number of flavors we’ll cover later. The third column tells you the definition.

Using Methods and Properties

Let’s play with the methods and properties for a minute. That’s good training because if you know how to invoke methods and properties of one object, you can do it with any object.

Important note: First of all, you are playing with live objects. So if you accessed your explorer.exe and wanted to try out the delete method just to see what happens next, I can tell you what will happen next. You will get mad. So take care! Next, there is one trick with methods. Whenever you call a method (get-member tells you if the thing you want to play with is a method or property, remember?), you must use parenthesis at the end. If you don’t, you get the method definition dumped at you instead.

First, let’s copy your file to another location. A quick look into the list of available methods and properties reveals that there is a CopyTo method. The definition tells you that it returns a FileInfo object and that it accepts a number of parameters. Most of the definition is cut off because the window width is too small but there’s enough room to see the first of a number of ways to call it: simply supply a destination file name. Let’s try:

$file.CopyTo("c:\newexplorer.exe")

The file gets copied to the new location, and you see something that looks like a directory entry for the new file. This is actually the text representation of the FileInfo object returned by CopyTo – or in other words: CopyTo returns the newly created file object, and since you did not save it in a variable, it gets dumped out as text. You could as well have written:

$newfile = $file.CopyTo("c:\newexplorer.exe")

If you try that now, you will receive an error though because the new file already exists (you copied it already in the previous example). So CopyTo – in its default behaviour – does not overwrite files.

How can you overwrite files? And: How can you get the complete definition for the CopyTo method?

Use get-member again! This time, pick the exact member you would like to get information for:

$info = $file | get-member -name CopyTo

You pipe your file object to get-member and tell get-member with its -name parameter that you are only interested in CopyTo. This way, the result is the definition for CopyTo. If you had omitted the -name parameter, get-member would have returned an entire array of all available members (methods and properties).

Now you have the complete description of CopyTo in your $info variable. To read the definition, you say:

$info.definition

How did I know the definition was stored in a property named definition? When I used get-member the first time, the column headers are the names of the properties, and here I could see that the definition is in the column definition.

You now get something like this:

System.IO.FileInfo CopyTo(String destFileName), System.IO.FileInfo CopyTo(String destFileName, Boolean overwrite)

In this comma separated list, you see the ways you can call CopyTo. There is another signature with two parameters where the second parameter determines whether or not the destination file can be overwritten. Let’s try:

$newfile = $file.CopyTo("c:\newexplorer.exe", $true)

It works. The file is copied and an existing file is overwritten.

Since now you have saved the result in $newfile, this variable contains the file object of the newly created file. It’s a FileInfo object just like the one you got initially from Dir. So you can use the very same methods and properties on that object as well. In fact, all files are represented that way, so by learning how to deal with FileInfo objects, you learn how to deal with any file in your local file system.

Let’s do something that’s entirely impossible in VBScript – let’s change a files’ creation date! While in Scripting.FileSystemObject most properties are read-only, .NET world is a lot more liberal:

CreationTime is a property so you don’t use parenthesis. The CreationTime definition tells you that it is {get;set} which really means it is read and write. To change the creation time, you say:

$newfile.CreationTime = "1.1.2022"

When you want to assign a new value to a property, you use an assignment. In fact, many people aren’t aware that properties are really no different from variables. You could see CreationTime as a variable which makes it easier to understand why you assign new values rather than using parameters. Put simple, properties are variables that are maintained by an external object while “regular” variables are maintained by the PowerShell host.

You now have used methods, you read a property and you changed a property value. You also learned how to use get-member to list all of an objects’ methods and properties or retrieve a specific member and examine its definition more closely.

That’s all sound and fine. However, when we accessed a file simply by using Dir, you may have felt a slight sting in your heart and maybe already suspected that all of this object blabla really isn’t necessary for most of the time. And you’re right. I just told you about it because object oriented access to information is pretty much all there is in VBScript and that's why VBScripters want to know where their objects are.

Creating New Files and Doing File I/O

In PowerShell, your primary tools are the built-in commandlets. There are plenty of them, and they allow you to do most everything with files and folders that you ever want to do. Without even touching object properties and methods.

Let’s start with a brand new file. In VBScript, you would - like always - have to fiddle around with object libraries and stuff:

Set fs = CreateObject("Scripting.FileSystemObject")

Set file = fs.CreateTextFile("c:\myfile.txt")

file.Write "Hello"

file.Close

In PowerShell, you use new-item because you want a new item:

New-item -ItemType file c:\myfile.txt -value "Hello"

You will notice that this line not only creates the file but dumps text information about the new file. You now know that this text really is the FileInfo object that New-Item returns, and when you create a new file, it would make a lot of sense to store that FileInfo object for later reference:

$myfile = new-item -ItemType file c:\myfile.txt -value "Hello"

In its default new-item will not overwrite files because it is polite and does rude things like that only if you tell it so. So you would tell new-item to "use the force", and it now happily nukes existing files:

$myfile = new-item -ItemType file -force c:\myfile.txt -value "Hello"

Or you simply use Dir to get access to the FileInfo object anytime later:

$myfile = Dir c:\myfile.txt

Catch: You can create text files even easier than that. Consider the following line:

"Hello" > c:\myfile.txt

However, in this case the file is ASCII, and if you try to manipulate the content later on with the *-content family of commandlets, you may end up with a mixture of ASCII and UNICODE.

You manage the content of a file with a number of commandlets that all share “content” as their noun. To read the content of a file, you write:

Get-content c:\myfile.txt

Or, since there are predefined aliases for any ethnic region you may come from:

Cat c:\myfile.txt

Type c:\myfile.txt

To add new stuff to an existing file (aka “ForAppending”), you write:

Add-Content c:\myfile.txt "`nAnother line…"

Note the strange escape character: In PowerShell, escape sequences such as new lines use a very... uh... strange format. Instead of the common backslash “\”, PowerShell chose the backwards apostrophe (`). Many people didn't even know this character existed.

Escape Characters You Should Know...

In fact, it pretty much looks like all other special characters had already been taken for other things when the team realized they forgot the escape character - and chose this one. It's really hard to find on a keyboard (especially on non-US-layout) and use. Here are some common escape sequences:

Escape Sequence

Description

`0

null

`a

alert

`b

backspace

`f

formfeed

`n

new line

`r

carriage return

`t

tab

`v

vertical quote

Finally, to completely replace the content of a file with new information, this is the way:

Set-Content c:\myfile "Started from scratch"

Accessing File Content 

Let’s check how you work with file content and consider the following code:

new-item -type file $env:temp\mytext.txt -value "line1`nline2`nLine3`nLine4" -force

$file = Dir $env:temp\mytext.txt

$file

Get-Content $env:temp\mytext.txt

It creates a new text file in your %temp% folder, overwrites an existing file and fills the file with four lines. It then accesses the file using Dir (yes, you could also have stored the FileInfo object returned by Dir in a variable in the first place. We are using Dir here to show you how to tap into an existing file even if you did not create it yourself).

Next, it dumps out the object as text representation which gives you the file details. Get-Content finally dumps the content of the file: four lines of text.

Get-Content does not really return text, though. As any other well-behaving commandlet, it returns objects. Actually, the file content is returned as an array of individual lines. So if you wanted to access line 2 (and remembered that arrays always start with index 0), you could write:

$content = get-content $file

$content[1]

Likewise, if you wanted to process all lines in your file one by one, let’s say because they resembled server names and you wanted to do stuff with each of them, do this:

$content | foreach-object { "Now I could examine server `"" + $_ + "`"" }

Since variables are automagically expanded inside of double-quoted text, you could also write:

$content | foreach-object { "Now I could examine server `"$_`"" }

For the sake of clarity, we can leave out the escaped quotes for a second. Get it now? ;-)

$content | foreach-object { "Now I could examine server $_" }

Or, you can filter the content and omit lines you do not need:

$content | select-string "2"

This would only output the line that reads “Line 2”. We’ll cover this in a later session in more detail and also introduce wildcards and pattern matching.

Support for Binary Files (and XML, too)

PowerShell also comes with excellent support for non-text files – something that was entirely left out in VBScript. If you try and dump a binary file, you end up with a lot of funny characters:

get-content $env:windir\explorer.exe

Like most commandlets, get-content comes with a ton of parameters and can be convinced to dump binary file content as well:

get-content $env:windir\explorer.exe -encoding byte

This gets you a loooong list of byte values which is ok but not great. So let’s examine and format the bytes one by one, pretty much like we did with the text lines of the text file:

get-content $env:windir\explorer.exe -encoding byte | %{ $_ }

Huh? Again a shortcut that makes PowerShell so unreadable: “%” is equal to foreach-object. The line does not do anything visibly different, though, so let’s spice up what’s inside the curly brackets.

get-content $env:windir\explorer.exe -encoding byte | %{ ("{0:x}" -f $_).PadLeft(2,"0") }

You now get hex numbers using the format function. PadLeft makes sure the hex numbers are padded to the left with zeros if they are just one digit. Confused? Consider this:

("{0:x}" -f 12).PadLeft(2,"0")

This returns the hex representation of “12”. “$_” is just the placeholder for the current byte value the loop is processing.

Now the result really looks ugly. Get-Content however can group data using the –ReadCount parameter. Now things look better:

get-content $env:windir\explorer.exe -encoding byte -readcount 10 | %

{

$line = $_

$text = $line | %{ " " + ("{0:x}" -f $_).PadLeft(2,"0")}

"$text"

}

We’ll cover those formatting options in a later session. Here, this example shows clearly that there’s great support even for raw binary files. And support also includes XML files. Again, that’s for another session.

Looking Ahead...

Back to common scripting tasks. With the knowledge you gathered so far, you can not only master the file system now. You can adapt it to other information stores as well, for example the windows registry.

PowerShell virtualizes a lot of information stores and makes them available as if they were really a filesystem type of thing:

Get-PSDrive

So to browse the registry, no longer do you need an awkward WScript.Shell object or access to the StdRegProv WMI class. Instead, you simply change directory to the registry root you want to visit:

Cd HKCU:

Dir

Cd Software

Md MySoftware

In fact, you can use all the item-commandlets you used on files also for registry access. There's another interesting commandlet family if you plan to play with the Registry: get-command -noun itemproperty

And you can create your own virtual “drives” as well. So a lot to cover, but not today. I’ll have a separate sessions on that on Day 4.

GetObject: Using WMI and ADSI

Let’s focus on two more areas for the moment: ADSI and WMI.

ADSI (Active Directory Service Interface) is a way for scripts to access local and remote user management functions, either in a local SAM database or via access to an Active Directory or compatible LDAP directory service. WMI (Windows Management Instrumentation) is also a local and remote thing but accesses the internal management service built into any windows since Windows 2000.

Both are accessible from VBScript via GetObject, and both are extremely valuable. Many scripts manage to do their work solely with these two object models. Too bad PowerShell does not have an equivalent to GetObject. Seriously.

Instead, Microsoft chose to give access to ADSI and WMI using legacy techniques (where legacy refers to Microsoft not using standard ways to do it).

A Quick WMI Primer

In VBScript, you would access instances of any WMI class using this code:

Set objWMI = GetObject("winmgmts:")

Set fishtank = objWMI.ExecQuery("select * from Win32_Service")

For Each fish In fishtank

       WScript.Echo fish.GetObjectText_

Next

In PowerShell, you do it this way:

get-wmiobject "win32_service"

If you are curious to see which other WMI classes are available, try this:

Get-wmiobject -list

You may wonder why get-wmiobject returns only some of the WMI class properties. VBScript retrieves a lot more information per instance. No worry, try this:

Get-wmiobject "Win32_Service" | format-list *

This is how you would get hold of a specific instance:

$alerter = get-wmiobject win32_service -filter "name='alerter'"

Next, you could run the object through get-member to find out about its properties and methods:

$alerter | get-member

If you wanted to stop this service, you called the StopService method:

$alerter.StopService()

And even remoting and authentication are included, so you could log onto another computer as a different user and control the remote WMI in just the same way:

get-wmiobject win32_service -credential PC0123\Administrator -computer pc0123

WMI is such a huge topic it fills books all by itself so since you now know the basics we’ll cover all the details in a separate session and move over to ADSI. Check out Day 5!

ADSI - Limitations (And Ways To Work Around Them)

ADSI is the sad part. While there was an excellent ADSI provider built into one of the very early PowerShell betas (or alphas if I remember right), they took it out. People protested. So right before release, the PowerShell team decided to at least build in a very simple ADSI connector.

That’s bad for a number of reasons (well it’s good they did at least that). It’s bad because it does not offer complete support. And it’s bad because PowerShell was all about consistency. Now, it does not make very much sense to use a strange technique to access a mainstream area like ADSI and use a completely different logic for it.

Having said that, let’s look at the facts.

In VBScript, you would again use GetObject for ADSI access and then supply a moniker string which could either target a local SAM database using the WinNT: moniker or any LDAP directory service using LDAP:.

For the sake of simplicity, this is how you access the local SAM database with all of your local users in BLOCKED SCRIPT

Set computer = GetObject("WinNT://.")

computer.Filter = Array("user")

For Each objInstance In computer

       WScript.Echo objInstance.name

Next

The good news is that you can continue to use those connection strings you used with GetObject.

$computer = [ADSI]"WinNT://."

However, that’s about it. A lot of functionality is missing, and there is no way I found so far to translate the VBScript to PowerShell. Do you have an idea?

Actually, $computer | get-member reveals that lots of properties and all methods are missing. That’s not entirely true (thanks god), there in fact are a lot of the methods you may be used to from ADSI – they are simply invisible. Another of those mysteries that will eventually go away with a more mature version of PowerShell.

So you can do things like add a new user, assign a new password, etc if you know the necessary methods:

$user = $computer.Create("user", "CofiDog")

However, once you try and set a property, PowerShell again causes confusion by throwing a strange error:

$user.Description = "Dog Account"

If you run $user through get-member you get nothing:

$user | get-member

The true reason is rather simple. In contrast to VBScript and the standard ADSI provider, you need to first instantiate your new user by calling the (invisible, thanks guys!) SetInfo method:

$user.SetInfo()

Once you did that, you can safely assign values to properties like Description (don’t forget to call SetInfo again to make changes permanent), and you can also set the password using the SetPassword method:

$user.SetPassword("topsecret")

The really really true reason why you could not set the Description property without first having to instantiate the object however is one of the many ADSI bugs in PowerShell. It’s not an ADSI limitation because if you set the property with Put, PutEx or InvokeSet, you can set it at any time. We’ll cover this in great detail starting with Day 6.

So some things you may be able to cover with native PowerShell, many others not. ADSI is one of the areas where traditional scripting languages like VBScript are a lot more powerful.

Merging VBScript Code in PowerShell Scripts

So for the sake of justice, after I have introduced so many PowerShell goodies to you VBScripters, I'd like in turn explain how PowerShell can use VBScript techniques to overcome its ADSI limitations. I want to show you how to merge VBScript code into your PowerShell code.

To execute VBScript code from inside PowerShell, you use the ScriptControl object which is part of every Windows installation even if you weren't aware of it until today. Here's a function you can use to inject VBScript code into a PowerShell console or script. 

function new-vbscript([string] $code = $(throw "Specify VBScript code"))

{

 $sc = new-object -comobject ScriptControl

 $sc.Language = "VBScript"

 $sc.AddCode($code)$

 $sc.CodeObject

}

Next, you define VBScript functions and procedures you would like to use in PowerShell. In fact, you can now import all of your existing VBScript functions and procedures without the need to change anything! Well, almost. Let's take a look:

PS C:\> $vbs = new-vbscript('
>> function ListUsers
>> set computer = GetObject("WinNT://.")
>> computer.Filter = Array("user")
>> for each user  in computer
>> list = list & user.name & vbNewLine
>> next
>> ListUsers = list
>> end function
>> ')
>>

This statement calls new-vbscript and defines the vbscript function to list all local users (you've seen that one already a couple of paragraphs above). That's something PowerShells own ADSI support was unable to do. Just make sure:

·         You tested your VBScript code thoroughly because you don't want to debug through PowerShell and end up in some VBScript code inside the ScriptControl

·         You may not use the WScript object in your scripts. This object comes from the WSH (Windows Script Host), and since your script now is executed by the Script Control, there is no WScript object. Instead of outputting data using WScript.Echo, you should do it like in the example and collect information in a variable that you then use as return value for your function.

new-vbscript returns the ScriptObject's CodeObject which is the VBScript scope. That's what you store in a variable, and that's also how you call your function. But first check this out: your new vbscript functions (yes, you could have defined more than one, that's entirely up to you and the VBScript code you supply) are even fully discoverable:

$vbs | get-member

And this is how you get the list of users:

$vbs.ListUsers()

Note that since ListUsers is a method (all functions and subs/procedures are methods), you must add parenthesis to it or else get the method definition.

So despite the fact that PowerShell has very lame native ADSI support, with your new-vbscript function you can now easily spice your PowerShell scripts with VBScript functions.

And if you followed closely, you have discovered the Language property used with the ScriptControl. So really all ActiveX languages are available, JScript as well as PerlScript or anything else you got.

However, don’t feel sad when you discover that the ADSI limitations really aren’t there. Starting with Day 6, I’ll cover ADSI in great detail and show you how to work around those limitations with native PowerShell.

Summary 

Today, we have covered a lot of ground. I have demonstrated how to access external object models and how to use get-member to explore and discover objects.

At the same time, you have seen that in real day life, you do not need to access objects all the time just to do simple stuff. PowerShell comes with great support for local files and folders and expands this support to other hierarchical information stores such as the registry or your environment variables.

So a lot of your scripts will shrink in size and complexity I am sure. That’s true for WMI scripts as well as PowerShell comes with good support for it, making up for the missing GetObject functionality.

The only sad area is ADSI where support is not even on beta level. It’s ugly and bad. With the help of the ScriptControl object and new-vbscript, you can however merge existing (and working) VBScript ADSI code into your PowerShell scripts.

Cheerio

- Tobias

 

Published Thursday, March 29, 2007 1:17 AM by tobias

Comments

 

aquariums said:

Two hours later, I could hardly drag him out of the water! He has always enjoyed salt water fish and aquariums, For him, snorkeling was just like being inside the aquarium! We saw several different types of coral, including brain coral, purple sea fans,

April 20, 2008 1:54 AM
 

websites directories said:

Press releases have some unique characteristics that can contribute to an increase in search engine positioning for your site. They are similar in many ways to pages that use search engine copywriting techniques. They have a narrow focus, include copy

July 28, 2008 4:33 PM
Anonymous comments are disabled