Welcome to TechNet Blogs Sign in | Join | Help

Hey, Scripting Guy! Why Would I Even Want to Create a Profile in Windows PowerShell?

Bookmark and Share


Hey, Scripting Guy! Question

Hey, Scripting Guy! Okay, I get that I can create a profile for Windows PowerShell. What I don’t understand is why I would want to create a profile in the first place. It seems to me that Windows PowerShell 2.0 works just fine the way it is. Can you enlighten me a bit?

-- VW

Hey, Scripting Guy! AnswerHello VW,

Microsoft Scripting Guy Ed Wilson here. Today, it is Meat Loaf—not the food, but the singer. After a couple days of beautiful sunny weather, the autumn grays have returned with a vengeance, and it is damp and cool outside. On such days, I like a little loud upbeat guitar rock with a strong pot of English Breakfast tea, lemon grass, and cinnamon to chase the cobwebs out of my head. I was up late last night working on the Scripting Guys Twitter account and am therefore a little sluggish this morning. Per my normal morning routine, I am perusing the e-mail that has been sent to scripter@microsoft.com. I ran across your e-mail, VW, and it reminded me that I had not mentioned why someone would want to use a profile. I guess I just thought it was intuitive. Sorry about that.

Image of Windows PowerShell 2.0 Best Practices

Note: Portions of today's Hey, Scripting Guy! post are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

For someone who is trying to migrate from VBScript to Windows PowerShell 2.0, the concept of a Windows PowerShell profile has to be totally foreign because there is nothing like it in VBScript. One of the problems with trying to learn Windows PowerShell is there are many concepts that are new. A good place to begin learning about Windows PowerShell is to check out our Windows PowerShell scripting page. There are links to Windows PowerShell scripts as well as information about the cmdlets and other fundamental Windows PowerShell concepts.

A Windows PowerShell profile commonly contains four items.

·         Variables

·         Aliases

·         Functions

·         Windows PowerShell Drives

We will examine adding variables to the Windows PowerShell profile today, but before we get too far, it is important to remember that a Windows PowerShell profile is a Windows PowerShell script. It has a special name, and it is located in a special place (in this regard you can consider it a sort of an AUTOEXEC.BAT file for Windows PowerShell), but it is still a script.

When Windows PowerShell is first installed, the script execution policy is set to Restricted, which means no scripts are permitted to run. Because a profile is a ps1 file, it is therefore a script and by default will not run. There are five levels of execution policy that can be configured in Windows PowerShell by using the Set-ExecutionPolicy cmdlet. These five levels are listed in Table 1. The Restricted execution policy can be configured via Group Policy by using the Turn on script execution policy setting in Active Directory. It can be applied to either the computer object or to the user object. The computer object setting takes precedence over other settings.

User preferences for the Restricted execution policy can be configured by using the Set-ExecutionPolicy cmdlet, but they will not override settings configured by Group Policy. An example of changing the current execution policy to RemoteSigned is seen here. To run the Set-ExecutionPolicy cmdlet, the Windows PowerShell console must have been launched with administrator rights. To do this, right click the shortcut to Windows PowerShell and click Run as administrator. If you attempt to run the Set-ExecutionPolicy cmdlet—even when logged on to the computer as the administrator or a user who is a member of the local administrators group—the error seen in the following image will appear if you are using Windows Vista or later.

Set-ExecutionPolicy -ExecutionPolicy remotesigned

Image of error related to Set-ExecutionPolicy cmdlet

The resultant set of Restricted execution policy settings can be obtained by using the Get-ExecutionPolicy cmdlet.

Table 1  Restricted Execution Policy Settings

Level

Meaning

Restricted

Will not run scripts or configuration files.

AllSigned

All scripts and configuration files must be signed by a trusted publisher.

RemoteSigned

All scripts and configuration files downloaded from the Internet must be signed by a trusted publisher.

Unrestricted

All scripts and configuration files will run. Scripts downloaded from the Internet will prompt for permission before running.

Bypass

Nothing is blocked and there are no warnings or prompts.

 

In addition to the five Restricted execution policy settings, you also have the option to configure the scope of the policy. When you set the scope of the Restricted execution policy, it determines how the policy is applied. There are three valid values: Process, CurrentUser, and LocalMachine. These values are detailed in Table 2.

Table 2  Scope of Restricted Execution Policy Settings

Scope

Meaning

Process

The execution policy affects only the current Windows PowerShell process.

CurrentUser

The execution policy affects only the current user.

LocalMachine

The execution policy affects all users of the computer.

After you have enabled script support on your computer, it is time to edit your Windows PowerShell profile. To edit your profile, you can use either Notepad or, because a Windows PowerShell profile is a script, your favorite Windows PowerShell script editor such as the Windows PowerShell ISE. (I almost never use an ISE to edit the profile because I do not like navigating to the $profile location. Instead, I use Notepad and the Windows PowerShell command line.) With Notepad, I can easily open the profile from the Windows PowerShell command like this:

PS C:\> notepad $PROFILE
PS C:\>

This assumes you have in fact created a profile. The creation of a Windows PowerShell profile was discussed in yesterday’s Hey Scripting Guy! post.

After you have opened the Windows PowerShell profile script, you will want to add comments to it that indicate a date and a version, and to supply placeholders for each of the four types of items that are normally stored in a Windows PowerShell profile. This is seen here:

Image of profile script with date, version, and four placeholder items

A Windows PowerShell profile with some common types of variables added to it is seen in the Profile.ps1 script shown here.

Profile.ps1

# My PowerShell Profile
# HSG-11-24-09
# Ed Wilson, MSFT, 11/18/2009
# Version 1.0

# *** Variables ***
New-Variable -Name ProfileFolder -Value (Split-Path $PROFILE -Parent) `
   -Description MrEd_Variable
New-Variable -Name IseProfile `
   -Value (Join-Path -Path (Split-Path $PROFILE -Parent) `
   -ChildPath Microsoft.PowerShellISE_profile.ps1) `
   -Description MrEd_Variable
New-Variable -Name MyComputers -Value Hyperv,Win7-PC -Description MrEd_Variable
Set-Variable -Name MaximumHistoryCount -Value 128 -Description MrEd_Variable
New-Variable -name temp -value $([io.path]::gettemppath()) -Description MrEd_Variable


The first variable to create is one called ProfileFolder. It points to the location where the $PROFILE resides. To find this location on computers that run various operating systems, it is necessary to use the Split-Path cmdlet to retrieve the parent portion of the location. One thing to keep in mind when creating variables using the New-Variable cmdlet is that the name of the variable does not include the dollar sign (this was something that tripped me up more than once when I was just learning Windows PowerShell.) The description parameter is used to assign a common name to all of the variables you create in your profile. The use of this parameter will be detailed a bit later:

New-Variable -Name ProfileFolder -Value (Split-Path $PROFILE -Parent) `
   -Description MrEd_Variable

The second variable I like to create in my Windows PowerShell profile is the IseProfile variable. Whereas when you are inside the Windows PowerShell ISE the $PROFILE variable refers to your ISE profile, outside of the Windows PowerShell ISE, the $PROFILE variable refers to your Windows PowerShell console profile. (For a more detailed explanation of this, refer to yesterday’s Hey, Scripting Guy! post.)

To make it easy to work with my Windows PowerShell ISE profile and to avoid confusion, I like to use the variable shown here:

New-Variable -Name IseProfile `
   -Value (Join-Path -Path (Split-Path $PROFILE -Parent) `
   -ChildPath Microsoft.PowerShellISE_profile.ps1) `
   -Description MrEd_Variable

I have a few computers on my network with which I routinely work. I get tired of typing individual computer names when I want to find out information about my remote machines. By using a variable to store the various computer names, I can use a command such as this one to test the connections:

PS C:\> Test-Connection -ComputerName $MyComputers

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
MRED1         Hyperv          192.168.1.100    {}                                       32       0
MRED1         Win7-PC         192.168.1.110    {}                                       32       0
MRED1         Hyperv          192.168.1.100    {}                                       32       0
MRED1         Win7-PC         192.168.1.110    {}                                       32       1
MRED1         Hyperv          192.168.1.100    {}                                       32       0
MRED1         Win7-PC         192.168.1.110    {}                                       32       1
MRED1         Hyperv          192.168.1.100    {}                                       32       0
MRED1         Win7-PC         192.168.1.110    {}                                       32       0


PS C:\>


 

To create the variable to hold the computer names, once again I use the New-Variable cmdlet as seen here:

New-Variable -Name MyComputers -Value Hyperv,Win7-PC -Description MrEd_Variable

Two more variables that are useful to add to a Windows PowerShell profile are the MaximumHistoryCount and temp variables. In the first case, MaximumHistoryCount, the variable already exists. It is used to control how many items your command history will remember. To modify the value, use the Set-Variable cmdlet. The second variable is the temp variable. This variable points to your temporary folder, a favorite location of many applications, and one that is not easily discoverable because of its highly nested location. By having an easy-to-use variable, you can easily work with items in this location. The code to work with these two variables is shown here:

Set-Variable -Name MaximumHistoryCount -Value 128 -Description MrEd_Variable
New-Variable -name temp -value $([io.path]::gettemppath()) -Description MrEd_Variable

Of course, VW, there is still not too much to our Windows PowerShell profile at this point. We will add to our profile tomorrow as Windows PowerShell Profile Week continues.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!


Ed Wilson and Craig Liebendorfer, Scripting Guys

Hey, Scripting Guy! How Can I Use Profiles with Windows PowerShell?

Bookmark and Share  


Hey, Scripting Guy! Question

Hey, Scripting Guy! I would like to personalize the way that Windows PowerShell works. I have been hearing that I can use a thing called a profile to do this, but when I try to find information about profiles, I come up blank. There is no New-Profile Windows PowerShell cmdlet so I do not see how to create such a thing. Can you help me please?

-- AC

Hey, Scripting Guy! AnswerHello AC,

Microsoft Scripting Guy Ed Wilson here. It is another beautiful day in Charlotte, North Carolina—at least it would be. I had an early morning visit to the doctor this morning, and you know how that can go. Interestingly enough, one of the nurses commented to the Scripting Wife that I was not a morning person because I was always grumpy when she saw me. Not a morning person? I get up early every day of the week. Grumpy? Me? I am one of the most laid back people you could ever meet. Then it dawned on me. The only time she sees me, she is giving me a shot or draining a couple quarts of blood from my veins. That must be why she thinks I am grumpy.

AC, it is interesting you talk about changing the way Windows PowerShell behaves because Windows PowerShell is highly configurable. The way you see it today may not be the way you see it tomorrow. If you were to use Windows PowerShell on my computer, it might behave differently. The environment controls it all—and the Windows PowerShell profile is a major part of that environment.

Image of Windows PowerShell 2.0 Best Practices book

Note: Portions of today's Hey, Scripting Guy! article are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. This book is available for pre-order.


Creating the profile

When Windows PowerShell is first installed, there are no profiles installed on the computer. In one respect you can consider the profile to be like the autoexec.bat file was a few years ago. The autoexec.bat file was simply a batch file that executed batch commands. However, because it was located in the root and had the name autoexec.bat, it took on an importance that was greatly out of proportion to a simple batch file because the commands that existed in the file were used to configure all kinds of activities including configuring the environment and even launching Windows itself. The Windows PowerShell profile does not launch Windows PowerShell. Instead, it is a Windows PowerShell script that happens to have a special name and happens to exist in a special place, or rather it happens to have two special names and exists in four special places. You heard me correctly. There are actually four Windows PowerShell profiles. These four profiles are listed in Table 1.

Table 1Windows PowerShell Profiles and Locations

Profile

Location

AllUsersAllHosts

C:\Windows\system32\WindowsPowerShell\v1.0\profile.ps1

AllUsersCurrentHost

C:\Windows\system32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1

CurrentUserAllHosts

C:\Users\UserName\Documents\WindowsPowerShell\profile.ps1

CurrentUserCurrentHost

C:\Users\UserName\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

 

In addition to the four Windows PowerShell profiles, other applications that host Windows PowerShell can create their own profiles. For example, the Windows PowerShell Integrated Scripting Environment (ISE) hosts Windows PowerShell and has two additional profiles. These two profiles are listed in Table 2.

Table 2 Windows PowerShell ISE Profiles and Locations

Profile

Location

CurrentUserCurrentHost

 $Home\[My ]Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1

AllUsersCurrentHost

 $PsHome\Microsoft.PowerShellISE_profile.ps1

Choosing the correct profile

Two of the profiles are used by all Windows PowerShell users on a computer. Anything placed in the All Users profile will be available to any script or to any user that runs Windows PowerShell on the computer. As a result, you should be rather circumspect in what you place in the All Users profile. However, these are great locations to configure aliases that you wish to make available to all users, variables you intend to use in a corporate scripting environment, or a Windows PowerShell drive or a function.

The next question is which one of the two All Users profiles should you use? The AllUsersAllHosts profile will apply to all of the users on the computer. It also applies to every instance of Windows PowerShell that may run on the computer. This includes the Windows PowerShell console, the Windows PowerShell ISE, and anything else that may host Windows PowerShell. This would include the Exchange Management environment, the SQL console, and any application that could host Windows PowerShell. Even if you are careful with the aliases you create, the variables you assign, the functions you write, and any Windows PowerShell drives you decide to make, you will still need to test to ensure compatibility. The AllUsersCurrentHost profile would give you the same ability to modify the Windows PowerShell environment for all users, but would only apply to the console host.

The two current user profiles are used to modify the Windows PowerShell environment for the current user. The profile that is most often modified by a user to configure personal Windows PowerShell settings is the CurrentUserCurrentHost profile. This one is referenced by the $profile automatic variable. On my computer, the value of the $profile variable is shown here:

PS C:\> $PROFILE
C:\Users\edwilson\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

You can see that, on a Windows Vista computer (or on a Windows 7 computer), the user's personal folder is in the user’s Documents folder. The WindowsPowerShell folder does not exist if no profile has been created. This is seen here, where the Test-Path cmdlet is used to see if the parent folder that should contain the Microsoft.Powershell_profile.ps1 file exists. Because no personal profile has yet been created on this laptop, the WindowsPowerShell folder has not been created:

PS C:\> Test-path (Split-Path $PROFILE -Parent)
False

Keep in mind that some applications will create an application-specific profile and store it in the WindowsPowerShell folder. On one of my computers, I do not have a Windows PowerShell profile configured. I do, however, have Sapien’s Primal Script installed, and Primal Script creates an application-specific profile. The test for the parent folder, therefore, will pass as shown here:

PS C:\> Test-Path (Split-Path $PROFILE -Parent)

True

PS C:\> Test-Path $PROFILE

False

PS C:\>

To explore what is contained in the $PROFILE –Parent folder, you can call Windows Explorer from the command line by using the Invoke-Item cmdlet. This is seen here:

PS C:\> Invoke-Item -Path (Split-Path $PROFILE -Parent)

PS C:\>

You can also call Windows Explorer directly by using the executable name as seen here:

PS C:\> explorer (Split-Path $PROFILE -parent)

Calling Windows Explorer directly does not really buy you anything, particularly because you can use aliases to shorten the typing. This is seen here:

PS C:\> ii (Split-Path $PROFILE -Par)

However you get there, the Windows Explorer directory is set to the WindowsPowerShell directory. This is seen here:

Image of Windows Explorer directory set to the Windows PowerShell directory


To create a CurrentUserCurrentHost profile, use the New-Item cmdlet as shown in the following code. When using the New-Item cmdlet, specify the –force if the folder does not exist, and specify the ItemType as file. This is shown here:

New-Item -Path $PROFILE -ItemType file –Force

After you create the profile, you can open it in Notepad or in the Windows PowerShell ISE to edit the file. If you choose to edit it in Notepad, it is as simple as typing Notepad and giving it the automatic $profile variable as shown here:

Notepad $profile

The newly created Windows PowerShell profile opens in Notepad and is ready for editing. This is seen here:

Image of Windows PowerShell profile ready for editing


Of course, AC, there is nothing in the Windows PowerShell profile at this stage of the game. We will add to our profile tomorrow as Windows PowerShell Profile Week continues.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (11/20/09)

Bookmark and Share

In this post:

 

Troubleshooting a Windows PowerShell Script Using the Replace Operator

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am learning Windows PowerShell in preparation for a Windows 7 and Server 2008 rollout. In the How Can I Use Windows PowerShell to Replace Characters in a Text File? post, you use the replace operator. I tried to use the replace operator, but it didn't work from an interactive shell. Does this only work from inside a script? Here is an example:

$a = "apple"

$a -replace "a", "b"


It would output what I wanted, but when I would enter $a, it would still print apple. It only overwrites the existing value of $a when I run this from a script, not interactively.

Do you have any suggestions?


-- SC

 

Hey, Scripting Guy! AnswerHello SC,

You are not storing the value that is being replaced inside a variable. You are merely replacing the value that is displayed, but not the value that is stored. You need to write back to the $a variable. This is seen here:

Image of writing back to the $a variable

 

 

How Can I Get a List of Certificates and Their Expiration Dates?

Hey, Scripting Guy! Question

Hey, Scripting Guy! Is there any way to get a list from Microsoft Certificate Services of the certificates and their expiration dates?

 

-- SM

 

Hey, Scripting Guy! Answer Hello SM, 

Using Windows PowerShell, it is easy. You can use the Get-ChildItem cmdlet on the Cert: drive. The Get-ChildItem cmdlet has an alias of Dir, so it is easy to remember. If you wish to see all of the certificates in all of the stores, you can use the -recurse switch. To see the expiration date of your certificates, use the Dir command on your certificate drive. The expiration date is the NotAfter property. This is seen here:

PS C:\> dir cert:\CurrentUser -Recurse

 

 

Name : SmartCardRoot

 

Name : UserDS

 

Name : AuthRoot

 

Subject      : CN=UTN-USERFirst-Object, OU=http://www.usertrust.com, O=The USERTRUST Network, L=Salt Lake City, S=UT, C

               =US

Issuer       : CN=UTN-USERFirst-Object, OU=http://www.usertrust.com, O=The USERTRUST Network, L=Salt Lake City, S=UT, C

               =US

Thumbprint   : E12DFB4B41D7D9C32B30514BAC1D81D8385E2D46

FriendlyName : USERTrust

NotBefore    : 7/9/1999 2:31:20 PM

NotAfter     : 7/9/2019 2:40:36 PM

Extensions   : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, S

               ystem.Security.Cryptography.Oid...}

 

To determine if a certificate is expired, check the value of the NotAfter property and compare it to the value of the current date. If the NotAfter property is less than the current date, the certificate is expired. This is seen here:

PS C:\> dir cert:\CurrentUser -Recurse | Where { $_.NotAfter -le (Get-Date) }

 

 

Name : SmartCardRoot

 

Name : UserDS

 

Name : AuthRoot

 

Subject      : OU=Class 3 Public Primary Certification Authority, O="VeriSign, Inc.", C=US

Issuer       : OU=Class 3 Public Primary Certification Authority, O="VeriSign, Inc.", C=US

Thumbprint   : 4F65566336DB6598581D584A596C87934D5F2AB4

FriendlyName : VeriSign

NotBefore    : 1/28/1996 7:00:00 PM

NotAfter     : 1/7/2004 6:59:59 PM

Extensions   : {}

 

 

Can I Change a Registry Value on 200 Computers?

Hey, Scripting Guy! Question

Hey, Scripting Guy! I need to change a value in the registry. The registry key is listed here:

HKEY_CURRENT_USER\Identities\{GUID for Identity}\Software\Microsoft\Outlook
Express\5.0

The problem is that I need to change this value on 200 computers. Is there a way to write some constant instead of the real GUID for Identity?

-- MK

 

Hey, Scripting Guy! AnswerHello MK,

I don’t think so because the GUID is what Outlook Express uses to keep track of the different users. What you will have to do is to read the value that is stored in the identities key and store the GUID that is returned in a variable. After you have that value, you can proceed to modify the child keys. Because you want to get this on 200 computers, you will need to use the WMI stdregprov class if you wish to run remotely, or you can use the VBScript registry classes if you will do this via login script.

Of course, Windows PowerShell makes it extremely easy to work with the registry, but based upon the registry key you are modifying, I doubt you have Windows PowerShell installed on all of your desktop machines yet.

Here are some registry scripts from the Script Repository that might help (you can refine the search results by selecting languages and other options from the lower left side of the screen).

In addition to the scripts from the Script Repository, you may be interested in some of the Hey, Scripting Guy! posts that talk about modifying the registry. The Hey, Scripting Guy! posts can provide you with more information than simple script samples (you can further refine the search results by clicking the keywords in the tag cloud above the list of articles).

 

 

How Can I Use Windows PowerShell to Delete Folders Within a Directory?

Hey, Scripting Guy! Question

Hey, Scripting Guy! As a complete newbie to Windows PowerShell scripting, I seek your advice. Usually, I would use a DOS batch file to complete this task, but Powershell is the way to go and so what better way to start learning than with a real life example?

I have a number of folders within a directory, all with files inside (the numbers at the end are randomly generated). The folders are shown here:

Folder14

Folder26

Folder01

I would like to delete the folders. I could use an old school loop through with FOR /F and RMDIR, but how would I do this using a Windows PowerShell script?

-- AS

 

Hey, Scripting Guy! AnswerHello AS, You could use the Del command (an alias for the Remove-Item cmdlet). Here is an example:

Dir C:\test  | Del -recurse

 

This code deletes all of the files and folders in the C:\test folder, but does not delete the C:\test folder itself. To delete the C:\test folder and all of its contents, use the command seen here:

del  C:\test –Recurse

 

   

Can I Use a Script to Connect to IPP Printers Through http://pserver/printer?

Hey, Scripting Guy! Question

Hey, Scripting Guy! I hope you can help me with this because I can’t find a straight answer elsewhere. I need to connect to IPP printers from client computers through http://pserver/printer via a script.

I can connect to IPP printers through http://pserver/printers. I choose the printer, click Connect, and it downloads the driver from the server. How can I achieve this via a script? I have tried PrintUI, but PrintUI does not pull the driver from the server; it installs the driver from the local ntprint.inf file that's on the local client computer, but I want to download the driver from the server where the IPP printers are installed. I always want to install the driver from the server in case there is a driver setting change, and by downloading the driver, it pulls the settings down to the client computer.

I can create an IPP printer with the PrintUI command by giving the port path, but the IPP printer is not the same as when I connect from the browser because it is not downloading the driver from the server and as a result I end up with a driver version mismatch. The PrintUI command downloads the driver only if you connect to the UNC path, \\server\printer. It does not recognize IPP path, http://pserver/printer. Also Con2prt does not recognize the http: path, but works great with the UNC path.

With what script language can I accomplish installing the IPP printers as if I were connecting via the browser? Any help will be appreciated.

-- NS

 

Hey, Scripting Guy! AnswerHello NS,

I have no idea. Sorry. Iif you figure it out, please let me know.

 

 

The Real Answer to the Previous Question 

Hey, Scripting Guy! Question

Hey, Scripting Guy! You told me to let you know if I figured out how to install the IPP printers. Here is what I did.

To connect the IPP printers via a browser, I created a .cmd script and entered bunch of “start URLs” for each printer. The “start URLs are the “connect” URL of each printer. I used the ScriptIt NT4.0 utility to answer the Windows Security prompts. I left the ScriptIt utility running until I was done connecting all the IPP printers. It works pretty well. ScriptIt works somewhat similar to the way VBScript sendkey does, but I am not familiar with VBScript that much. This utility works well.

Here is an example:

Run the utility: Scriptit.exe IPP-Prompt.ini

Run the batch file that contains the following:

start http://PrintServer/printers/ipp_0015.asp?eprinter=NICU7NurStation&view=q

FOR /F "tokens=1,2,3 delims=: " %%i IN ('time /t') DO waitfor.exe pauseFor%%i%%j%%k /t 45

 

start http://PrintServer/printers/ipp_0015.asp?eprinter=NICU6BloodGas&view=q

FOR /F "tokens=1,2,3 delims=: " %%i IN ('time /t') DO waitfor.exe pauseFor%%i%%j%%k /t 45

 

Well, this concludes another edition of Quick-Hits Friday. It also concludes another exciting week on the Script Center. Join us next week as we delve into the mysteries of…well, we will let that remain a mystery for now.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Quickly Check Stocks with Windows PowerShell?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have a customized MSN home page. I really like the new layout, but it is rather inefficient to open Internet Explorer, and wait for a few minutes just to check out the latest stock prices. What I need is a Windows PowerShell script that I can use to quickly retrieve my stock information without the need to open Internet Explorer and scroll down half the page to find a current stock quote. I wrote a Windows PowerShell script that opens a page in Internet Explorer and scrapes the information, but it is only marginally faster than clicking on the Internet Explorer icon on my quick launch bar. Can you write a Windows PowerShell script to retrieve quickly a stock quote?

-- HM

Hey, Scripting Guy! AnswerHello HM,

Microsoft Scripting Guy Ed Wilson here. the cool thing about using Windows PowerShell 2.0 to connect to Web services is there are hundreds of Web services on the Internet. Some are highly specialized and rather complex, and others such as the one for retrieving stock quotes are very straightforward. By performing a Bing search for “Web service” or for “WSDL,” you can find hundreds of free and easy-to-use Web services that will do everything from resolving a ZIP code to performing currency conversions.

HM, the Get-StockQuote.ps1 script uses a Web service to retrieve the latest quote for a given stock. The complete Get-StockQuote.ps1 script is seen here.

Get-StockQuote.ps1

#Requires -Version 2.0

Function Get-StockQuote($symbol)

{

 $URI = "http://www.webservicex.net/StockQuote.asmx?WSDL"

 $stockProxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy -class stock

 $stockProxy.getQuote($symbol)

} #end Get-StockQuote

 

$quote = Get-StockQuote -symbol msft

[xml]$quote |

ForEach-Object {

 $_.stockQuotes.Stock

} #end Foreach-Object

The Get-StockQuote.ps1 script begins with the #Requires –Version 2.0 tag. This is a best practice to prevent the script from attempting to run on a computer with Windows PowerShell 1.0 installed. Because the New-WebServiceProxy cmdlet does not work on Windows PowerShell 1.0, the script would generate an error. The #Requires command is shown here:

#Requires -Version 2.0

It then uses the Function keyword to create the Get-StockQuote function. The Get-StockQuote function accepts a single parameter—the stock symbol. This is shown here:

Function Get-StockQuote($symbol)

{

The path to the StockQuote Web Service Definition Language (WSDL) is assigned to the $URI variable. The WSDL describes the Web service. As seen in the following image, the StockQuote Web service contains a method named GetQuote that accepts a stock symbol as a string:

Image of GetQuote method


The path to the StockQuote WSDL is assigned to the $URI variable as shown here:

$URI = "http://www.webservicex.net/StockQuote.asmx?WSDL"

The New-WebServiceProxy cmdlet is used to create a new Web service proxy for the StockQuote Web service. To do this, the New-WebServiceProxy cmdlet requires the path to the WSDL for the StockQuote Web service. The resulting proxy is stored in the $stockProxy variable as shown here:

$stockProxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy -class stock

The getQuote method from the StockQuote Web service is used to retrieve the stock quote information. The getQuote method requires a stock symbol as a string to return the information. This is shown here:

$stockProxy.getQuote($symbol)

} #end Get-StockQuote

The entry point to the script calls the Get-StockQuote function and passes a string value for the –symbol parameter. The returned data is stored in the $quote variable. This is seen here:

$quote = Get-StockQuote -symbol msft

The XML type accelerator ([xml]) is used to cast the data that is returned from the Get-StockQuote function into an XML document. The ForEach-Object cmdlet is used to walk through the document, and the stock property is used to return the stock quote. This code is seen here:

[xml]$quote |

ForEach-Object {

 $_.stockQuotes.Stock

}

When the Get-StockQuote.ps1 script runs, the following output is displayed:

Image of script output

 

Well, HM, as you can see, Web services are fun and can be relatively easy to use from within Windows PowerShell 2.0. Join us tomorrow as we open the virtual mailbag and address questions that require short answers. That’s right, it is time for Quick-Hits Friday. 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Use a Web Service to Find Weather for a Specific City?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I thought your script yesterday was pretty cool, but there is a problem with it. Retrieving the weather from a script is awesome; however, I need to know which cities are available. If I want to know the weather in Sydney, Australia (your example), I am all set. I’m sure you can see this coming: I do not live in Sydney and therefore the script is useless. How about displaying a list of available cities so that I can figure out how to use the script?

-- NR

Hey, Scripting Guy! AnswerHello NR,

I am listening to the Travelling Wilburys on my new Zune HD. It is an absolutely gorgeous day outside—deep blue cloudless skies and a temperature of 69 degrees Fahrenheit (20.5 degrees Celsius). I have opened all the windows in my scripting house, and I am reclining in my large leather scripting chair checking the scripting e-mail sent to scripter@microsoft.com on my Windows 7 scripting laptop. I have a fresh pot of scripting English Breakfast tea, lemon grass, and cinnamon stick. It is a perfect way to enjoy the mellow Charlotte, North Carolina, weather after the weeklong deluge gifted to the area by tropical storm Ida. NR, you are right about the practicality of knowing the weather in Sydney, Australia. Whereas I might dream of heading back to Australia, with the realities of the current budget crunch, I probably will not make it back to Australia before 2011. Therefore, I better see if I can check out the weather report for Charlotte, North Carolina.

NR, the Get-InternationalCities.ps1 script uses the globalweather Web service and the GetCitiesByCountry method to return a list of available cities. The complete Get-InternationalCities.ps1 script follows.

Get-InternationalCities.ps1

$country = "united states"
$URI = "http://www.webservicex.net/globalweather.asmx?wsdl"
$Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy
$countries = [xml]$proxy.GetCitiesByCountry($country)
$countries.NewDataSet.Table | Sort-Object -Property city |
Select-Object -Property city

The first thing the Get-InternationalCities.ps1 script does is assign a value for the $country variable. The value of the $country variable is used to retrieve the list of cities. This is shown here:

$country = "united states"

After the country has been set for the script, you need to assign the path to the globalweather Web Service Definition Language (WSDL) to the $URI variable. The WSDL is an XML document that describes the service offering. Because the WSDL string is a Uniform Resource Locator (URL), you can open it directly in Internet Explorer and look for methods and properties described by the WSDL. The following image illustrates this:

Image of methods and properties described by the WSDL


After you assign the path to the $URI variable, you can use the New-WebServiceProxy cmdlet to create a Web service proxy that allows you to connect and to retrieve information from the globalweather Web service. The returned object is a special webserviceproxy object that is created from the Web service that is specified in the $URI variable. The members of the WebServiceProxy.GlobalWeather object are shown here:

   TypeName: WebServiceProxy.GlobalWeather

Name                                 MemberType Definition                                             
----                                 ---------- ----------                                             
Disposed                             Event      System.EventHandler Disposed(System.Object, System.Ev...
GetCitiesByCountryCompleted          Event      WebServiceProxy.GetCitiesByCountryCompletedEventHandl...
GetWeatherCompleted                  Event      WebServiceProxy.GetWeatherCompletedEventHandler GetWe...
Abort                                Method     System.Void Abort()                                    
BeginGetCitiesByCountry              Method     System.IAsyncResult BeginGetCitiesByCountry(string Co...
BeginGetWeather                      Method     System.IAsyncResult BeginGetWeather(string CityName, ...
CancelAsync                          Method     System.Void CancelAsync(System.Object userState)       
CreateObjRef                         Method     System.Runtime.Remoting.ObjRef CreateObjRef(type requ...
Discover                             Method     System.Void Discover()                                 
Dispose                              Method     System.Void Dispose()                                  
EndGetCitiesByCountry                Method     string EndGetCitiesByCountry(System.IAsyncResult asyn...
EndGetWeather                        Method     string EndGetWeather(System.IAsyncResult asyncResult)  
Equals                               Method     bool Equals(System.Object obj)                         
GetCitiesByCountry                   Method     string GetCitiesByCountry(string CountryName)          
GetCitiesByCountryAsync              Method     System.Void GetCitiesByCountryAsync(string CountryNam...
GetHashCode                          Method     int GetHashCode()                                      
GetLifetimeService                   Method     System.Object GetLifetimeService()                     
GetType                              Method     type GetType()                                          
GetWeather                           Method     string GetWeather(string CityName, string CountryName) 
GetWeatherAsync                      Method     System.Void GetWeatherAsync(string CityName, string C...
InitializeLifetimeService            Method     System.Object InitializeLifetimeService()              
ToString                             Method     string ToString()                                      
AllowAutoRedirect                    Property   System.Boolean AllowAutoRedirect {get;set;}            
ClientCertificates                   Property   System.Security.Cryptography.X509Certificates.X509Cer...
ConnectionGroupName                  Property   System.String ConnectionGroupName {get;set;}           
Container                            Property   System.ComponentModel.IContainer Container {get;}      
CookieContainer                      Property   System.Net.CookieContainer CookieContainer {get;set;}  
Credentials                          Property   System.Net.ICredentials Credentials {get;set;}         
EnableDecompression                  Property   System.Boolean EnableDecompression {get;set;}          
PreAuthenticate                      Property   System.Boolean PreAuthenticate {get;set;}              
Proxy                                Property   System.Net.IWebProxy Proxy {get;set;}                  
RequestEncoding                      Property   System.Text.Encoding RequestEncoding {get;set;}        
Site                                 Property   System.ComponentModel.ISite Site {get;set;}            
SoapVersion                          Property   System.Web.Services.Protocols.SoapProtocolVersion Soa...
Timeout                              Property   System.Int32 Timeout {get;set;}                        
UnsafeAuthenticatedConnectionSharing Property   System.Boolean UnsafeAuthenticatedConnectionSharing {...
Url                                  Property   System.String Url {get;set;}                           
UseDefaultCredentials                Property   System.Boolean UseDefaultCredentials {get;set;}        
UserAgent                            Property   System.String UserAgent {get;set;}             

The name webserviceproxy comes from the value we specified for the namespace parameter with the New-WebServiceProxy cmdlet. This is seen here:

$URI = "http://www.webservicex.net/globalweather.asmx?wsdl"
$Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy

The properties of the new Web service proxy are displayed by printing the value stored in the $Proxy variable as shown here:

PS C:\Users\ed.NWTRADERS> $proxy


SoapVersion                          : Default
AllowAutoRedirect                    : False
CookieContainer                      :
ClientCertificates                   : {}
EnableDecompression                  : False
UserAgent                            : Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protoco
                                       l 2.0.50727.4927)
Proxy                                :
UnsafeAuthenticatedConnectionSharing : False
Credentials                          :
UseDefaultCredentials                : False
ConnectionGroupName                  :
PreAuthenticate                      : False
Url                                  : http://www.webservicex.net/globalweather.asmx
RequestEncoding                      :
Timeout                              : 100000
Site                                 :
Container                            :

Once the WebServiceProxy.GlobalWeather object is created, the GetCitiesByCountry method is called. The Get-Member cmdlet can be used to provide a clue to the input required by the GetCitiesByCountry method (although it would seem rather obvious that the method wants a string that represents the country to query). This is shown here:

PS C:\Users\ed.NWTRADERS> $Proxy | Get-Member -Name GetCitiesByCountry


   TypeName: WebServiceProxy.GlobalWeather

Name               MemberType Definition                                  
----               ---------- ----------                                   
GetCitiesByCountry Method     string GetCitiesByCountry(string CountryName)

The data that is returned by the GetCitiesByCountry method is in the form of a data set with tables that represent each city/country combination. This is seen here:

PS C:\Users\ed.NWTRADERS> $Proxy.GetCitiesByCountry("mexico")
<NewDataSet>
  <Table>
    <Country>Mexico</Country>
    <City>Acapulco / G. Alvarez</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Aerop. Internacional Monterrey, N. L.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Aguascalientes, Ags.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Bahias De Huatulco</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Cuernavaca, Mor.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Ciudad Del Carmen</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Culiacan, Sin.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Chetumal, Q. Roo</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Santa Rosalia, B. C. S.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Campeche, Camp.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Ciudad Juarez International</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Chihuahua International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Ciudad Victoria Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Cozumel Civ / Mil</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Durango Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tepic, Nay.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Don Miguel / Guadalaj</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Guaymas International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Hermosillo, Son.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Colima</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Saltillo, Coah.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Los Mochis Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Del Bajio / Leon</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>La Paz International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Loreto, B. C. S.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Matamoros International</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Aerop. Internacional Merida, Yuc</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Mexicali International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Morelia New</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Minatitlan</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Monclova, Coah.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Mexico City / Licenci</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Monterrey / Gen Maria</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Mazatlan / G. Buelna</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Nuevo Laredo International</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Oaxaca / Xoxocotlan</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Puebla, Pue.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Piedras Negras, Coah.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Uruapan / Gen Rayon</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Puerto Vallarta / Lic</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Puerto Escondido</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Queretaro, Qro.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Reynosa International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>San Jose Del Cabo</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>San Luis Potosi, S. L. P.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Torreon, Coah.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tuxtla Gutierrez, Chis.</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tijuana International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tulancingo</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tampico / Gen Fj Mina</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Toluca / Jose Maria</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Tapachula</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Cancun International Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Villahermosa</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Gen. Heriberto Jara</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Zacatecas Airport</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Ixtapa-Zihuatanejo</City>
  </Table>
  <Table>
    <Country>Mexico</Country>
    <City>Manzanillo International</City>
  </Table>
</NewDataSet>

Because of the way the list of cities returns, it is necessary to retrieve the tables that contain the data. The tables are pipelined to the Sort-Object cmdlet where the cities are sorted in alphabetical order. The cities are then selected by using the Select-Object cmdlet. This is shown here:

$countries = [xml]$proxy.GetCitiesByCountry($country)

$countries.NewDataSet.Table | Sort-Object -Property city |

Select-Object -Property city

When the Get-InternationalCities.ps1 script runs inside the Windows PowerShell ISE, the following results are seen:

Image of results of running the script

 

Well, NR, as you can see, XML provides a great way to organize data and to easily retrieve properties of objects. Join us tomorrow as XML Week continues.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Use Web Services?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I remember reading about things called “Web services” a long time ago. It seems that I do not hear much about these anymore. Do they really exist, and if so what are they good for?

-- GK

Hey, Scripting Guy! AnswerHello GK,

Microsoft Scripting Guy Ed Wilson here. We have had to batten down the hatches here in Charlotte, North Carolina, as the tropical storm formerly known as Hurricane Ida makes a guest appearance in our neighborhood. The 50-foot tall southern pines that inhabit my yard are doing their version of the hula, and the handmade specially tuned wind chime on our front porch is playing a ghostly tune. The sky is dark and the wind whips down the street like a freight train crossing the great planes on a mission of sinister importance. Strangely, eerily perhaps, it seems there is no noise—and then it hits—all at once a great cacophony of rushing wind, overturned lawn furniture banging and clanging as it rolls across yards, and all the time the 4/4 staccato of the wind chime marks the beat of an unseen conductor.

GK, because we often receive extreme weather during hurricane season, I am extremely interested in the weather. However, I am even more interested in writing scripts. It would be awesome if I could combine both activities. What one needs is an easy-to-use way to query for current weather information. I know of dozens of Web sites I can go to (including the newly redesigned MSN page) where I can find current weather information. I can, of course, open the Web page in a script and display it, but I prefer a quick and easy way to obtain only the weather information, and to bypass the browser completely. To do this, I need to use a Web service, which returns information in an optimized manner. The data returns in XML format, and an application or script easily parses the information. This requires a little understanding of XML. The complete Get-InternationalWeather.ps1 script shown here illustrates the technique.

Get-InternationalWeather.ps1

#Requires -version 2.0

Function Get-Weather
{
 Param(
  [Parameter(Mandatory=$true)]
  [string]$city,
  [Parameter(Mandatory=$true)]
  [string]$country
 )#end param
 $URI = "http://www.webservicex.net/globalweather.asmx?wsdl"
 $Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy
 $Proxy.GetWeather($city,$country)
} #end Get-Weather

[xml]$xml = Get-Weather -city "sydney airport" -country "Australia"
$xml.CurrentWeather

The Get-InternationalWeather.ps1 script begins by declaring a function named Get-Weather. The Get-Weather function accepts two input parameters, city and country. Both parameters are strings, and both are mandatory. The [Parameter(Mandatory=$true)] tag is new for Windows PowerShell 2.0 and is used to force a parameter to be present when the script runs. If the script runs without values for either the country or the city parameter, the following error appears:

Image of error that appears when script is run without either the country or city parameter

The Get-Weather function parameter section is shown here:

Function Get-Weather
{
 Param(
  [Parameter(Mandatory=$true)]
  [string]$city,
  [Parameter(Mandatory=$true)]
  [string]$country
 )#end param

The Uniform Resource Identifier (URI) string points to the Web Services Description Language (WSDL) page. The WSDL describes the Web service offerings. Each service is a collection of network end points. The WSDL uses XML to define the query method. In the WSDL seen here, the GetWeather method takes two inputs: CityName and CountryName, both of which are strings:

  <?xml version="1.0" encoding="utf-8" ?>
- <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.webserviceX.NET" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://www.webserviceX.NET" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
- <wsdl:types>
- <s:schema elementFormDefault="qualified" targetNamespace="http://www.webserviceX.NET">
- <s:element name="GetWeather">
- <s:complexType>
- <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" name="CityName" type="s:string" />
  <s:element minOccurs="0" maxOccurs="1" name="CountryName" type="s:string" />
  </s:sequence>
  </s:complexType>
  </s:element>

The $URI variable holds the path to the globalweather WSDL. The string assignment listing follows:

$URI = http://www.webservicex.net/globalweather.asmx?wsdl

The key feature of the Get-InternationalWeather.ps1 script uses the New-WebServiceProxy Windows PowerShell cmdlet to make the connection to the globalweather Web service. After the connection to the Web service is established, the resulting connection is stored in the $proxy variable as seen here:

$Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy

It is finally time to retrieve the weather. To do this, call the GetWeather method from the globalweather Web service. The GetWeather method call receives both a city and a country as seen here:

$Proxy.GetWeather($city,$country)
} #end Get-Weather

When calling the GetWeather method, keep in mind that the returned data is XML. The raw XML returned by the GetWeather method is shown here:

<?xml version="1.0" encoding="utf-16"?>
<CurrentWeather>
  <Location>Sydney Airport, Australia (YSSY) 33-57S 151-11E 3M</Location>
  <Time>Nov 12, 2009 - 01:00 PM EST / 2009.11.12 1800 UTC</Time>
  <Wind> from the S (180 degrees) at 22 MPH (19 KT):0</Wind>
  <Visibility> greater than 7 mile(s):0</Visibility>
  <SkyConditions> partly cloudy</SkyConditions>
  <Temperature> 68 F (20 C)</Temperature>
  <DewPoint> 64 F (18 C)</DewPoint>
  <RelativeHumidity> 88%</RelativeHumidity>
  <Pressure> 30.03 in. Hg (1017 hPa)</Pressure>
  <Status>Success</Status>
</CurrentWeather>

[xml]$xml = Get-Weather -city "sydney airport" -country "Australia"

By exploring the contents of the $xml variable, you can see there are two properties that are reported. The first is XML and the second property is CurrentWeather. This is seen here:

PS C:\Users\ed.NWTRADERS> $xml | get-member -MemberType property


   TypeName: System.Xml.XmlDocument

Name           MemberType Definition                                
----           ---------- ----------                                
CurrentWeather Property   System.Xml.XmlElement CurrentWeather {get;}
xml            Property   System.String xml {get;set;}     

The CurrentWeather property returns a CurrentWeather object. This object contains a number of properties that are seen here:

PS C:\Users\ed.NWTRADERS> $xml.CurrentWeather | Get-Member -MemberType property


   TypeName: System.Xml.XmlElement

Name             MemberType Definition                              
----             ---------- ----------                              
DewPoint         Property   System.String DewPoint {get;set;}       
Location         Property   System.String Location {get;set;}        
Pressure         Property   System.String Pressure {get;set;}       
RelativeHumidity Property   System.String RelativeHumidity {get;set;}
SkyConditions    Property   System.String SkyConditions {get;set;}  
Status           Property   System.String Status {get;set;}         
Temperature      Property   System.String Temperature {get;set;}    
Time             Property   System.String Time {get;set;}           
Visibility       Property   System.String Visibility {get;set;}     
Wind             Property   System.String Wind {get;set;}           

Because each of the weather elements is available as an individual property, it is possible to access specific values such as the temperature. This is seen here:

PS C:\Users\ed.NWTRADERS> $xml.CurrentWeather.Temperature
 66 F (19 C)

For the Get-InternationalWeather.ps1 script, the complete weather information is shown. To do this, the CurrentWeather property is queried as seen here:

$xml.CurrentWeather

The result of running the Get-InternationalWeather.ps1 script is shown here:

Image of result of running script

 

Well, GK, as you can see, Web services are alive and well. In fact, they have taken on new importance because of their ease of use from within Windows PowerShell. Join us tomorrow as XML Week continues.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Is There an Easier Way to Work with XML Files?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have a file that I need to parse. It is an XML file, and it is very ugly. Who in the world ever thought that an XML file would be better than a plain CSV file? Unfortunately, Microsoft seems to be enamored with XML, and I cannot get away from it. Surely, there must be a better way to work with XML than to go cross-eyed looking at all those slashes and angle brackets! Can you throw a fellow a bone?

-- TS

Hey, Scripting Guy! AnswerHello TS,

Microsoft Scripting Guy Ed Wilson here. It is Friday in Charlotte, North Carolina, in the United States. There was frost on the lawn this morning, and I was tempted to make a mug of hot chocolate and forego my usual pot of English Breakfast tea, but I prefer real hot chocolate (not the powdered stuff) and I did not feel like fooling with the double boiler on a cold November morning. (I use milk, cream, whole vanilla beans, cinnamon sticks, almonds, hazelnuts, honey, and chunks of Mexican chocolate when I make hot chocolate so it is a big production.) So I ate my omelet while sipping my hot tea and checking my e-mail on my Windows Mobile 6.5 smart phone, and then headed up to my office primed and ready for a great day. It is after all no-meetings Friday.

TS, I am sincerely sorry that XML files upset you (my feelings were similar when I first saw XML). If you were here, I would fix you a cup of my special “Scripting Guy Hot Chocolate.” We would sit down, and I would show you that with Windows PowerShell 2.0, XML files need not be a hassle. Because you are not here, I suggest you make yourself some hot chocolate, and open up Windows PowerShell 2.0, and we’ll work through this together.

One thing that makes working with XML documents easier is having the right tool. If you double-click the XML file, the default association is Internet Explorer. This view lets you open and close nodes by clicking the minus signs on the left side of the code, as seen here:

Image of viewing the XML file in Internet Explorer


Using Internet Explorer to view an XML file is better than using Notepad. The color highlighting makes it easier to find nodes, and you can hide nodes you are not interested in seeing by clicking the minus (-) symbol to the left of the node. There is a better tool to use; it is the XML Notepad. The XML Notepad is freely downloadable from the Microsoft Download Center. Using the XML Notepad, you can easily figure out the structure of your XML file and find the names of the various nodes. This is shown here:

Image of viewing the same file with XML Notepad


The books.xml file that is loaded into XML Notepad, seen in the previous image, is included in various software development kits (SDKs), but you do not need to download and install an entire SDK just to gain access to this rather small XML file. You can easily create it by copying it from MSDN, pasting it into Notepad, and saving it as books.xml (You can use normal Notepad or XML Notepad to do this, whichever one you prefer to use.) It is useful to have a well-formed XML file upon which to practice your techniques because if you run into problems, you will at least know the XML file is working properly.

The easiest way to read an XML file is to use the Select-XML cmdlet. You can use the –path parameter to point to an XML file, and the –Xpath parameter to provide the query. The command and associated results are shown here:

PS C:\> Select-Xml -Path C:\fso\books.xml -XPath "//title"

Node                                    Path                                    Pattern
----                                    ----                                    -------
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title
title                                   C:\fso\books.xml                        //title

One of the problems with the output above is that it is useless. If you examine the output, it lists title, the file name, and the search criteria; however, it provides no detail about the title, which is the information we were really interested in obtaining in the first place. To obtain the actual title of the book, you can pipe the results to the Select-Object cmdlet. However, before doing that, you need to know which kind of object you are working with, so that you will know which properties are available to select. To do this, you use the Get-Member cmdlet and choose the property MemberType. The Get-Member command and the resulting properties of the SelectXMLInfo object are in this code list:

PS C:\> Select-Xml -Path C:\fso\books.xml -XPath "//title" | Get-Member -MemberType property

   TypeName: Microsoft.PowerShell.Commands.SelectXmlInfo

Name    MemberType Definition
----    ---------- ----------
Node    Property   System.Xml.XmlNode Node {get;set;}
Path    Property   System.String Path {get;set;}
Pattern Property   System.String Pattern {get;set;}

The SelectXMLInfo object only exposes three properties. From our earlier output, TS, you know that the path refers to the path to the XML file, and the pattern refers to the XPath query; therefore, the node must be the title information. You can use the Select-Object cmdlet to expand the node property. The revised Windows PowerShell command is contained in the code here:

PS C:\> Select-Xml -Path C:\fso\books.xml -XPath "//title" | Select-Object -ExpandProperty node

#text
-----
XML Developer's Guide
Midnight Rain
Maeve Ascendant
Oberon's Legacy
The Sundered Grail
Lover Birds
Splish Splash
Creepy Crawlies
Paradox Lost
Microsoft .NET: The Programming Bible
MSXML3: A Comprehensive Guide
Visual Studio 7: A Comprehensive Guide

TS, you might be wondering what the XPath parameter is all about. XPath is a way of querying XML files. There are numerous books written about XPath, and hundreds of pages on MSDN, but a good starting point is to take a look a look at the syntax documentation on MSDN. From this page, you can see that the //title query I used told the Select-Xml cmdlet to look for the use of title anywhere within the XML document. If you want to return the name of the root node, you can use the “/*” syntax seen in the listing here:

PS C:\> Select-Xml -Path C:\fso\books.xml -XPath "/*"

Node                                    Path                                    Pattern
----                                    ----                                    -------
catalog                                 C:\fso\books.xml                        /*

To find a book with the id attribute that is equal to bk101, you use the // to find all of the books, and then use a set of square brackets to indicate you are looking for an attribute that is equal to a certain value. This command is in the code that follows.

Keep in mind that XML values and attribute names are case sensitive. For example, book must begin with a small “b”, and “id” is lower case, as in “bk101.”

PS C:\> Select-Xml -Path C:\fso\books.xml -XPath "//book[@id = 'bk101']"

Node                                    Path                                    Pattern
----                                    ----                                    -------
book                                    C:\fso\books.xml                        //book[@id = 'bk101']


PS C:\>

Well, TS, as you can see, working with XML files does not need to be complicated. Join us tomorrow as XML Week continues.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (11/13/09)

Bookmark and Share

In this post:

 

Dealing with Quarantined File Types and Exchange Server

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have spent most of this week Googling, experimenting, and fighting with Exchange 2007. The problem I have been working on was caused by one of my colleagues who came to me and asked if I can make it possible for him to receive an .exe attachment that one of our customers wants to send to him. Being pretty sure that I could, I logged on to the Exchange server just to allow the attachment to be sent, and to my surprise, I discovered that Exchange has deleted it. I can’t get Exchange 2007 to quarantine .exe files. The only thing it can do is delete all of the.exe attachments, or let them all into my network (a change that obviously sets my security level below 0). Is there a way of setting Exchange 2007 attachment filtering rules, so I can decide which .exe files can be received?

-- MK

Hey, Scripting Guy! AnswerHello MK,

Microsoft Scripting Guy Ed Wilson here. This is an Exchange Server question, and I am no longer an Exchange Server guru. The last version I was an expert on was Exchange 5.5. At that time, there was no such thing as file quarantine. These days, what I do when I need to receive an executable file from someone is have them rename the .exe file to with a .ex_ extension and email it to me. Most of the time this simple change will get the file through. At times, however, it will not (it seems to depend on certain antivirus products that use advanced file-matching algorithms). For those occasions when I am unable to receive a renamed executable via e-mail, I use SkyDrive from Windows Live. It is free and allows up to 25 GB of storage. It works most excellently for sharing files. My SkyDrive is seen here:

Image of Ed's SkyDrive



 

Scanning Multiple Remote Computers and Creating a Resultant CSV File

Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I scan a list of multiple remote PCs (running Windows XP) on the same domain for particular installation files on their hard drives and submit the positive results to a .csv file? I came across one of your scripts (listed below) but this is only for one PC at a time and all of the results are shown in the shell.

ScanPCForSpecificFile.vbs

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService. _
    ExecQuery("Select * From CIM_DataFile Where FileName = 'VisioMUI' and Extension = 'msi'")
If colFiles.Count = 0 Then
    Wscript.Echo "The file does not exist on the remote computer."
Else
    Wscript.Echo "The file exists on the remote computer."
End If

-- JD

 

Hey, Scripting Guy! AnswerHello JD,

Believe it or not, the script is pretty well set up to run on multiple computers. What you will need to do is populate the value of the strComputer variable. The ScanPcForSpecificFile.vbs uses a period, which is WMI shorthand for “run on the local computer.”

There are several ways that you can get values for the strComputer variable. You can read a text file (really easy to do), you can ping a range of IP addresses (really cool to do), you can read a database (a bit of work but groovy none the less), or you can even query Active Directory (similar to querying a database) to get your computer names.

After you have the computer names, modify the wscript.echo commands to write to a text file, Word document, Excel spreadsheet, Access or SQL database, or whatever other output you like. Because of the work involved in opening and reading your data source, it often makes sense to stay with the same type of output for logging your results. For example, if you open a text file and read it for your computer names, use a text file to log your results.

You can find numerous examples of working with text files in the Hey, Scripting Guy! archive and in the TechNet Script Center Gallery. The same is true for working with Office Word, Excel, databases, and Active Directory.

Here is an example I wrote to illustrate what needs to be done.

ReadFromTextFileWriteToTextFile.vbs

'==========================================================================
'
'
' NAME: ReadFromTextFileWriteToTextFile.vbs
'
' AUTHOR: ed wilson , Microsoft
' DATE  : 10/20/2009
'
' COMMENT: Demo read from a text file, and writing information to a different
' text file. If the other text file does not exist, it will be created.
'==========================================================================
strServers = "c:\fso\servers.txt"
strOutFile = "c:\fso\output.txt"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile(strServers)
Set objOutFile = objFSO.OpenTextFile(strOutFile,8,True)
Do until  objTextFile.AtEndOfStream
    strComputer = objTextFile.Readline
    'WScript.Echo strComputer

   objOutFile.WriteLine strComputer
Loop
objTextFile.Close
objOutFile.Close

Here is your script pasted into the above script:
strServers = "c:\fso\servers.txt"
strOutFile = "c:\fso\output.txt"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile(strServers)
Set objOutFile = objFSO.OpenTextFile(strOutFile,8,True)
Do until  objTextFile.AtEndOfStream
strComputer = objTextFile.Readline
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService. _
    ExecQuery("Select * From CIM_DataFile Where FileName = 'VisioMUI' and Extension = 'msi'")
If colFiles.Count = 0 Then
    Wscript.Echo "The file does not exist on the remote computer."
Else
   objOutFile.WriteLine  "The file exists on the: " & strComputer
End If
Loop
objTextFile.Close
objOutFile.Close

I have not tested the script, but I believe it should work.   


How Can I Check for the Latest Daylight Saving Time Update?

Hey, Scripting Guy! Question

Hey, Scripting Guy! I’m hoping that you could help me here. Please see Bug ID 422388 on the Microsoft Connect Feedback Web page. This is a bug I ran into while scanning my domain during the 2009 DST spring forward. 

The problem is with recently patched Windows XP and Windows Server 2003 machines; WMI appears to follow the pre-2007 DST rules. The case on the Connect forum says that it has been fixed and closed, but it makes no mention of what has been fixed and how the end user might be able to remedy this on their own machines.

I was hoping that you might know internal Microsoft people who can shed some light about whether any operating system hotfixes are being issued to fix this problem.

You obviously see the seriousness of this issue when the instrumentation interface that you are using is giving incorrect replies.

Thank you for your attention.

-- WD


Hey, Scripting Guy! AnswerHello WD,

This is a Windows PowerShell script I wrote to check for the latest daylight saving time update. Daylight saving time updates are now cumulative. As far as I know, the latest one is 955839.

CheckDSTPatch.ps1

# CheckDSTPatch.ps1
# ed wilson, msft
# checks for patch 955839 Dec. 2008 TZ patch update
http://bit.ly/yukqp $computer = $env:computerName Get-WmiObject -Class Win32_QuickFixEngineering -computer $computer | Where-Object { $_.hotFixID -match '955839' }


 

Troubleshooting the Use of WSMan with Windows PowerShell

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am having an issue with Windows PowerShell. I hope someone can help. I could not find any relevant documentation or thread referring to same. While running a script using WSMan in Windows PowerShell, we created the script to get the data for CIM_Sensor class. The script ran successfully in a C# project, but when we ran the code in a Windows PowerShell script, it threw an exception. The exception that was thrown is seen here:

"Get-WSManInstance: The response that the WS-Management service computed exceeds the internal limit for envelope size."

Is this a Windows PowerShell issue because we were able to retrieve data successfully using C# and Perl? Any pointers would be highly appreciated.

-- SK
 

Hey, Scripting Guy! AnswerHello SK,

You can configure the envelope size limit (and a bunch of other things) in the WSMan provider. To do this, you will need to start Windows PowerShell as an administrator. Use the Set-Location cmdlet to change to the WSMan drive. You can then Set-Location to the localhost container. Use the Get-Item cmdlet to retrieve your maxenvelopesizekb property. Use the Set-Item cmdlet to configure a new size. This is seen here:

PS C:\Windows\system32> Set-Location wsman:
PS WSMan:\> Set-Location localhost

Start WinRM Service
WinRM service is not started currently. Running this command will start the WinRM service.

Do you want to continue?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): y
WARNING: Waiting for service 'Windows Remote Management (WS-Management) (WinRM)' to finish starting...
PS WSMan:\localhost> Get-Item .\MaxEnvelopeSizekb


   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost

Name                      Value                                                             Type
----                      -----                                                             ----
MaxEnvelopeSizekb         150                                                               System.String

 

For more information on this, refer to the WSMan provider help by using the following command: Get-Help wsman.

 

Well, this concludes another edition of Quick-Hits Friday. It also concludes another exciting week on the Script Center. Join us next week as we delve into the mysteries of…well, we will let that remain a mystery for now.

If you want to know exactly what we will be looking at on Monday, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you on Monday. Until then, have an awesome weekend.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Create Variables in Windows PowerShell?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! In VBScript, it was easy to create a variable; all I had to do was use the DIM statement. I did not need to specify a type or a value or anything. I just used DIM and the variable would be created. How do I create variables in Windows PowerShell? Please tell me you haven’t made it harder.

-- TL

Hey, Scripting Guy! Answer Hello TL,

Microsoft Scripting Guy Ed Wilson here. Today has been a very eventful day for me. I have had four meetings—all of which were essential. One meeting in particular, the meeting with Karen who helps us manage the Script Center Web site was especially productive. We are talking about modifying the Script Center home page to make it more user-friendly (don’t worry, it will not be a radical change like our recent migration). We all had some good ideas about what needs to be done, but I think Karen wins the best suggestion award for her idea to…well, you will just have to wait and see. I can tell you that we are really psyched!

Over the course of the last five months, we have gone from a great Web site with no bells or whistles to a great Web site with so much more available to us in terms of making the site easier for you to use. However, we won’t ever use any technical Web gewgaws just for the sake of using gewgaws. I am listening to White Snake (the early stuff, not the later commercial dross) on my Zune, and sipping a cup of Rooibos tea and nibbling on a small piece of 85 percent cacao from France while I review the e-mail sent to scripter@microsoft.com.

Note: Portions of today's Hey, Scripting Guy! post are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

TL, as with creating aliases (which was discussed in Monday’s Hey Scripting Guy! post), there are several different ways to create a variable and to assign a value to it. You can use the New-Item cmdlet on the variable drive as seen here:

New-Item -Name temp -Value $env:TEMP -Path variable:

You could also use the Set-Item cmdlet to do this. The advantage to the Set-Item cmdlet is that it does not generate an error if the variable already exists. Here is an example of using the Set-item cmdlet to create a variable. One thing to keep in mind is that the Set-item cmdlet does not have a parameter named –name:

Set-Item -Value $env:TEMP -Path variable:\temp

Neither the New-Item nor the Set-Item cmdlet has the ability to specify the option or the description parameter. With variables this is an important distinction because you cannot create a constant and you cannot create a read-only variable without using either Set-Variable or New-Variable. If a variable already exists and you use the Set-Variable cmdlet, the value of the variable will be overwritten if it has not been marked read-only or constant. If it is read-only, you can still modify the value of it by specifying the Force parameter. If the variable is marked as a constant, the only way to modify the value of the variable is to close the Windows PowerShell console and start over with a new value.

You can also create a variable and assign a value to it at the same time. This technique if often used when the value to be stored in the variable is the result of a calculation or concatenation. In this example, we decide to create a variable named $wuLog to store the path to the Windows Update log, which is stored in a rather obscure location in the user’s local Application Data folder. Though there is an environmental variable for local application data, the path continues to go on for a couple of levels before terminating in the WindowsUpdate.log file. As a best practice when building file paths, you should use the path cmdlets such as Join-Path to avoid concatenation errors. By using the environmental localappdata variable and Join-Path with the –resolve switched parameter, we also have a formula that will store the path to the WindowsUpdate log file on any user’s computer. This is exactly the kind of variable you would want to create and store in a user’s Windows PowerShell profile. This command is seen here.

PS C:\> $wuLog = Join-Path -Path $env:LOCALAPPDATA `
-ChildPath microsoft\windows\windowsupdate.log -Resolve
PS C:\> $wuLog
C:\Users\edwils.NORTHAMERICA\AppData\Local\microsoft\windows\windowsupdate.log

When using a variable to hold a computed value, you are not limited to using a direct value assignment. You can use the New-Variable cmdlet to perform exactly the same task:

PS C:\> New-Variable -Name wulog -Value (Join-Path -Path $env:LOCALAPPDATA `
-ChildPath microsoft\windows\windowsupdate.log -Resolve)

When using the New-Variable cmdlet to create a variable that will hold a computed result, you will often need to use parentheses to force the value to be created before attempting to assign it to the Value parameter. You may see an error about some missing or invalid parameter. This is because when the New-Variable cmdlet sees a parameter outside of a set of parentheses, it attempts to locate that parameter. An example of such an error is seen in the following image.

Image of missing parameter error

 

You can also use some of the automatic variables when creating variables for your profile. A large number of applications place files in the user’s documents directory. Though this is convenient for applications and for users who access the documents location via a link on the Start menu, it is nearly impossible to locate via the command line. An additional problem is that the Documents folder may not be displayed on the Start menu of the user. As seen in the following image, the folder may have been deselected:

Image of deselected Documents folder

 

To facilitate ease of access to the user’s Documents folder, you may decide to create a variable that could easily be used to refer to the path. This is another good opportunity to use the Join-Path cmdlet to aid in building the location to the Documents folder. There is an automatic variable that already points to the user's home directory. The home directory on my Windows Vista laptop points to the %username% folder under the user’s folder. This is seen here:

PS C:\> $home
C:\Users\edwils.NORTHAMERICA

Because the Documents folder is located under this home directory, as seen in the following image, we can add to this location and build the path to the documents directory:

Image of Documents folder under home directory


By using the New-Variable cmdlet, you can specify the Value parameter, which is contained in a set of parentheses because of the need to resolve the value of the Join-Path command before assigning it to the docs variable. The variable will be read-only, which allows you to modify the variable if needed, but it will also be protected from accidental deletion or modification. The Description parameter provides an easy way to keep track of all the custom variables. This line of code is shown here:

New-Variable -Name docs -Value (Join-Path -Path $home -ChildPath documents) `
-Option readonly -Description "MrEd Variable"

When I was first learning Windows PowerShell, I was often frustrated when I attempted to use the New-Variable, Set-Variable, and Remove-Variable cmdlets because a variable is prefixed with the dollar sign when working at the command line, but the name parameter does not use the dollar sign as part of the name of the variable.

Another way to obtain the path to the Documents folder is to use the WshShell object from VBScript fame. Because Windows PowerShell provides easy access to Component Object Model (COM) objects, there is no reason to avoid these devices. One way to use this COM object is to create and use the object all in the same line. This is seen here:

$docs = (New-Object -ComObject Wscript.Shell).specialFolders.item("MyDocuments")

From a best practice standpoint, there are at least two problems with the above syntax. The most obvious issue is that it is not very readable. While this is rather common usage, it would better to split the command into two lines of code as seen here:

$wshShell = New-Object -ComObject Wscript.Shell
$docs = $wshShell.SpecialFolders.Item("MyDocuments")

 

Well, TL, this should get you started working with variables in Windows PowerShell. Join us tomorrow as we open the virtual mail bag and answer questions that require only shorter answers.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Can I Change on the Fly the Way Windows PowerShell Functions Work?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have started using functions in Windows PowerShell to encapsulate complex commands, and it works pretty well. However, I would like to be able to change the way the functions work on the fly. By this I do not mean that I will throw the computer across the room and see how the function works, but rather I want to modify what the function does based upon information I give it. Can I do this?

-- LK

Hey, Scripting Guy! AnswerHello LK,

Microsoft Scripting Guy Ed Wilson here, I can sympathize with the desire to encourage one’s computer to take flight. Keep in mind that it is generally not the computer’s fault—it is the software. In the message/messenger dynamic, hardware is almost always the messenger hoping not to be shot. The good thing about Windows PowerShell is that it is highly configurable. If you do not like the way a particular cmdlet works, you can create a function and modify it. Let us look at how to accomplish this task.

Note: Portions of today's Hey, Scripting Guy! article are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

When using a function it is quite common to want to accept two or more parameters for input. This adds flexibility and usefulness to the function. The first method of passing parameters in Windows PowerShell is to use the $args automatic variable as seen in yesterday’s Hey, Scripting Guy! post.

Another way to pass parameters is to use named parameters. When using named parameters with a script, the Param statement precedes them. To use a named parameter within a function, you do not need to use the Param statement. You simply supply variables in each position for which you wish to have a parameter. The name of the variable becomes the name of the parameter. There are a few tricks to keep in mind when using both methods of passing multiple parameters. To that end, let us first examine the $args variable in a bit more detail.


Multiple parameters with $args

One way to pass two parameters is to use the $args automatic variable, and when passing two values to the function, you index into the array to retrieve a specific value. In the Get-WmiClass function, two values are passed when calling the function. The first value is used to hold the WMI namespace to search for WMI class names, and the second value is the type of WMI class for which to search. This function is useful for locating WMI classes. Use of the Get-WmiClass function is seen here:

Image of using the Get-WmiClass function


The Get-WmiClass function begins by retrieving two values from the $args variable. The $args variable is an automatic variable and is populated with whatever is fed to the function. The element from the first position is stored in the $ns variable, and the second element is kept in the $class variable. This is seen here:

$ns = $args[0]
$class = $args[1]

The Get-WmiObject cmdlet has a list switched parameter that will produce a list of all the WMI classes in the namespace. The namespace used is the one specified in the first position of the command used to call the function. The resulting list of all the WMI classes in the particular namespace is shunted to the pipeline. This line of code is seen here:

Get-WmiObject -List -Namespace $ns |

To make the list of WMI classes useful, the Where-Object cmdlet is used to filter out the unwanted WMI class names. Inside the code block for the Where-Object cmdlet, the automatic variable $_ is used to refer to the current item on the pipeline. The –match operator allows you to use a regular expression if desired to filter out the list of WMI class names. This line of code is shown here:

Where-Object { $_.name -match $class }

The complete Get-WmiClass.ps1 script is shown here.

Get-WmiClass.ps1

Function Get-WmiClass()
{
 #.Help Get-WmiClass "root\cimv2" "Processor"
 $ns = $args[0]
 $class = $args[1]
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass


Multiple named parameters

When you have more than two parameters to supply to a function, it can get really confusing to keep track of both the position and the meaning of the parameters. In addition, when using named parameters, you can apply type constraints to prevent basic types of errors that could occur when supplying values from the command line.

In Get-WmiClass2.ps1, the Get-WmiClass function has been rewritten to take advantage of command-line arguments. The primary change was moving the $ns and the $class variables inside the parentheses following the name of the function. In addition, because both the namespace and the class names should be strings, we use the [string] type constraint to prevent the inadvertent entry of an illegal value such as an integer. Because the revised function is using named parameters, the two lines that parsed the $args variable have also been removed. The Get-WmiClass2.ps1 script file is therefore shorter than the Get-WmiClass.ps1 script, and it has more capability. The first line of the Get-WmiClass function is seen here:

Function Get-WmiClass([string]$ns, [string]$class)

An example of value of the type constraints is seen here:

Image of type constraints

 

In the first example shown in the previous image, the Get-WmiClass function is called with the value of 5 for the –ns parameter. This violates the type constraint of [string] for the ns parameter. The resulting error is “Invalid parameter.”

In the second example shown in the previous image, the Get-WmiClass function is called with the value of root/cimv3 for the –ns parameter. Because there is no root/cimv3 namespace in the WMI hierarchy (at least not yet), the function actually executes. The resulting error comes from WMI, which states the problem is an “Invalid namespace.” As a best practice, you should always apply type constraints to your function parameters. The rudimentary protection afforded by them easily justifies the minimal effort required to type them.

To call the Get-WmiClass function, you can use the entire parameter name, a shortened unique version of the parameter name, or no parameter at all. Examples of each way to call the function are shown in the following code. When supplying a parameter, you only need to type enough of the parameter name to ensure that it is unique. As a best practice, you should take this feature into account when naming parameters. If each parameter begins with a unique letter, users of the function can supply single-letter parameter names, and still maintain a rudimentary level of readability. As an example, in the Get-WmiClass function, if we had named the namespace namespace and the class name simply name, we would have been required to type the entire word name for the name parameter and names for the namespace. That does not shorten very well.

Get-WmiClass -ns "root\cimv2" -class "disk"
Get-WmiClass -n root\cimv2 -c disk
Get-WmiClass root\cimv2 disk

When using named parameters with functions, you do not need to include a string inside quotation marks unless it contains a comma, semicolon, or other special character that could be misinterpreted by the runtime engine. When working from the command line, I often take advantage of this technique to reduce typing. However, when working in a script, I like to include the quotation marks to improve readability and understandability of the code.

The complete Get-WmiClass2.ps1 script is seen here.

Get-WmiClass2.ps1

Function Get-WmiClass([string]$ns, [string]$class)
{
 #.Help Get-WmiClass -ns "root\cimv2" -class "Processor"
 
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass

You can also create an alias for the function at the same time you define the function. Because this was for the Get-WmiClass function, we used the Get-Alias cmdlet to check for the existence of the chosen alias letter combination of gwc (selecting the first letter of each main word in the function name. You can use this command to see if the gwc alias is available:

Get-Alias -Name gwc

This is one of those times when you hope to receive an error because it means your chosen alias can be used. The error is seen in the following image:

Image of the error we were hoping to see


The completed Get-WmiClassWithAlias.ps1 script is seen here.

Get-WmiClassWithAlias.ps1

Function Get-WmiClass([string]$ns, [string]$class)
{
 #.Help Get-WmiClass -ns "root\cimv2" -class "Processor"
 
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass
New-Alias -Name gwc -Value Get-WmiClass -Description "Mred Alias" `
-Option readonly,allscope

 

Well, LK, this should get you started with passing parameters to functions. Join us tomorrow as we continue examining ways to customize our Windows PowerShell environment.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

New! On-demand translation of our blog!


You may have noticed something new about the Hey, Scripting Guy! Blog today (very top of the page):

Image of translation widget 

If you would prefer to read this blog in another language, you can now do that--on demand! Available languages are:

  • Arabic
  • Chinese Simplified
  • Chinese Traditional
  • Czech
  • Danish
  • Dutch
  • English
  • French
  • German
  • Greek
  • Hebrew
  • Italian
  • Japanese
  • Korean
  • Polish
  • Portuguese
  • Russian
  • Spanish
  • Swedish
  • Thai

Just choose the version of "Translate this page" from the drop-down list that is in the language in which you want to read a Hey, Scripting Guy! post, and within 15-30 seconds the content will be translated. The longer the content, the longer the translator will take to translate it.

If it seems the translator "isn't working," be sure to look at the green progress bar to see how much has already been translated:

 Image of translation progress

We will be working on this feature in the coming weeks. If you have feedback for us about it, please send e-mail to scripter@microsoft.com.

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Create a Custom Function?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am getting tired of typing the long commands used in Windows PowerShell. I know you Scripting Guys seem to show people always typing commands inside the Windows PowerShell console, but I do not think I like to work that way. It is TOO MUCH TYPING. I would love to be able to shorten some of the commands, but I do not think that is possible.

-- DR

Hey, Scripting Guy! AnswerHello DR,

Microsoft Scripting Guy Ed Wilson here, it is a cold day in Charlotte, North Carolina, in the United States. The sky is clear, and the sun is shining, but it’s an autumn sun and is not doing that much good. I even saw some people wearing jackets outside. For the sunny south, 50 degrees Fahrenheit (10 degrees Celsius) is enough to drive the children inside and bring the big wooly mothball-smelling coats outside. I am sipping a cup of aged Earl Gray tea with a cinnamon stick and a dash of warm milk to smooth the flavor. Along with the tea, I have a slice of Fuji apple and Wisconsin sharp cheddar cheese. It is a departure from my normal afternoon snack, but the combination seems to work well with autumn. The neighborhood Halloween decorations persist in some yards of overzealous homeowners in the subdivision, but that has not stopped others from putting out giant blow-up turkeys in anticipation of Thanksgiving. Yet with all that going on outside, the stores are skipping past Thanksgiving in hopes that St. Nick will soon be there. It is a confusing time. Therefore, a confused snack.

DR, I am not confused by your request. I have felt the same way myself. What I generally do is create a custom function that does what I want, and then create an alias for the function. I then add these items to my Windows PowerShell profile. Let’s begin by looking at creating a custom function.

Note: Portions of today's Hey, Scripting Guy! post are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

Functions provide a nearly endless capability of customization from within Windows PowerShell. The profile is a great place to supply some of this customization. As an example, suppose that when using the Get-Help cmdlet you prefer to see the full article, but you also know that in most cases the article is too long to fit on a single screen; therefore, you pipeline the output to the more function, which provides paging control. If you were looking for information about the Get-Process cmdlet, the command would be the one shown here:

Get-Help Get-Process -Full | more

There is nothing wrong with typing the above command except that, even when paired with tab expansion, it is more than 30 keystrokes. It does not take very long before you get tired of typing such a command. This is a perfect candidate for a function. When naming functions, it is a best practice to use the Verb-Noun naming convention because this syntax will be familiar to users of Windows PowerShell, and you can take advantage of tab expansion. As seen here, I named our function Get-MoreHelp.

Get-MoreHelp.ps1                                                                             

Function Get-MoreHelp()
{
 Get-Help $args[0] -Full |
 more
} #end Get-MoreHelp

The Get-MoreHelp function begins by using the Function keyword to declare the function. After the Function keyword, we specify the name of the function, which in this example is Get-MoreHelp. The empty parentheses are not required after the function name. The parentheses would be used to define parameters, and without any parameters, the parenthesis are not required. I generally include them as an indicator that a parameter could be specified in the position. This is seen here:

Function Get-MoreHelp()

Following the Function keyword, the function opens the code block by using an opening curly bracket. When typing the function I always open the code block with one curly bracket, and immediately on the next line type the closing curly bracket. In this way, I never forget to close a code block. As a best practice, I always include a comment that indicates the bottom curly bracket closes the function. The end comments also are a tremendous help when it comes time to troubleshoot the script because they promote readability and make it easier to understand the delimiters of the function. In addition, if you have a long function that scrolls off the screen, the end comment, with its repetition of the function name, makes it easier to create the alias for the function as well. This is seen here:

{

} #end Get-MoreHelp

The Get-MoreHelp function uses the $args automatic variable to hold the argument that was passed to the function when it is called. Because the Get-Help cmdlet does not accept an array for the name parameter, we use [0] to index into the first element of the $args array. If, as is required, there is only one item passed to the function, the item will always be element 0 of the array. The function passes the –full switched parameter to the Get-Help cmdlet. The resulting help information is passed along the pipeline via the pipe (“|”) symbol. This is shown here:

Get-Help $args[0] -full |


Overriding existing commands

Because it is possible for the Get-MoreHelp function to return more than a single screen of textual information, the function pipes the help information to the more function. Functions are first-class citizens in Windows PowerShell, and they have priority over executables and even native Windows PowerShell cmdlets. Because of this, it is easy to modify the behavior of an executable or cmdlet by creating a function with the same name as an existing one. This is illustrated by the more function. More.com is an executable that provides the ability to return information to the screen one page at a time—it has been available since the DOS days. The more function is used to modify the behavior of more.com. The content of the more function is shown here:

param([string[]]$paths)
if($paths)
{
    foreach ($file in $paths)
    {
        Get-Content $file | more.com
    }
}
else
{
    $input | more.com
}

By looking at the content of the More function, we see that there has been a useful addition to the functionality of more.com. If you supply a path to the More function, it will retrieve the content of the file and pipe the result to the more.com executable. This is shown here:

Image of supplying a path to the More function


Alias the function

When I create utility functions, I generally like to create an alias for the function. This will enable quick and easy access to the function. It is possible to create the function and the alias in the same script, but not within the function definition. The problem is that within the function definition, the function has not yet been created and therefore you cannot create an alias for a function that does not yet exist. But there is nothing wrong with creating the alias and the function in the same script. Interestingly enough, you can create the alias on a line before the function is declared or after the function is declared. Position does not matter.

Get-MoreHelpWithAlias.ps1

Function Get-MoreHelp()
{
 Get-Help $args[0] -full |
 more
} #End Get-MoreHelp
New-Alias -name gmh -value Get-MoreHelp -Option allscope


Loop the array

The $args variable returns an array; we can use this to our advantage and add the ability to pass two or more pieces of information and receive help for each topic. To do this, we use the For statement to loop through the elements of $args. The For statement uses three parameters: the beginning, the destination, and the method of travel. In this example, the variable $i is used to keep track of the position within the array. The variable $i is set equal to 0, and the –le operator (less than or equal to) is used to allow the loop to continue for the number of times that is represented by the count of the number of items in $args. As the loop progresses, the value of $i is incremented by one each time through the loop. The $i++ construction does this. This line of code is seen here:

For($i = 0 ;$i -le $args.count ; $i++)

One small change is required to the line of code that calls the Get-Help cmdlet. Instead of using $args[0], which will always retrieve the first element in the array, we change the 0 to $i. As the value of $i increases for each loop, the Get-Help cmdlet will query the next item in the array. This modified line of code is seen here:

Get-Help $args[$i] -full |

The remainder of the Get-MoreHelp function is the same as previous versions, which were discussed earlier. The complete function is seen in the Get-MoreHelp2.ps1 script.

Get-MoreHelp2.ps1

Function Get-MoreHelp
{
 # .help Get-MoreHelp Get-Command Get-Process
 For($i = 0 ;$i -le $args.count ; $i++)
 {
  Get-Help $args[$i] -full |
  more
 } #end for
} #end Get-MoreHelp
New-Alias -name gmh -value Get-MoreHelp -Option allscope

To run the Get-MoreHelp function, you can use the alias gmh and supply it one or more cmdlet names to get the help. This is seen in the following image where the function code was typed directly into the Windows PowerShell console:

Image of using the gmh alias

 

Well, DR, this should get you started on creating custom functions and aliases. Join us tomorrow as we continue examining ways to customize our Windows PowerShell environment.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Tell Me About Aliases in Windows PowerShell

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been playing around with Windows PowerShell 2.0 in Windows 7 and I think that I like it. However, it seems to require an awful lot of typing. The double command names, such as Get-Process, are somewhat helpful for remembering things, but it is quite a bit of typing. Yes, it is shorter than writing a VBScript to return the same information, but did you have to make everything so long? And by the way, while I am whining, I hate typing the hyphen. It is too hard to find on my keyboard. I do not expect you to change Windows PowerShell just for me, but surely there are others out there who feel the same way I do. If Windows PowerShell is supposed to be the all-new management tool for administrators, you could have made it easier to use.

-- RR

Hey, Scripting Guy! AnswerHello, RR.

Microsoft Scripting Guy Ed Wilson here. Well I just got off a two-hour Live Meeting with a customer. I was talking about some of the new remoting features in Windows PowerShell 2.0. It was an awesome talk, if I do say so myself. I love talking to customers, and I love working with Windows PowerShell 2.0. On those occasions when I combine those two passions, it is a great day.

RR, it seems that I need to introduce you to the concept of aliases. Consider a command such as Measure-Object that is used to count information and provide statistical information such as the minimum and maximum values of an object. Measure-Object can be a bit cumbersome to type from a command line, and given the relative frequency of its use, it becomes a good candidate for aliasing.

Note: Portions of today's Hey, Scripting Guy! post are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

Verifying the existence of an alias

Before creating a new alias, it is a best practice to see if there is a suitable alias already created for the cmdlet in question. By default, Windows PowerShell ships with more than 130 predefined aliases for its 271 cmdlets. When you consider that several of the cmdlets have more than one alias defined, you can see there is great opportunity for the creation of additional aliases. The ListCmdletsWithMoreThanOneAlias.ps1 script lists all cmdlets that have more than one alias defined. This script is seen here.

ListCmdletsWithMoreThanOneAlias.ps1

Get-Alias |
Group-Object -Property definition |
Sort-Object -Property count -Descending |
Where-Object { $_.count -gt 2 }

 

When the ListCmdletsWithMoreThanOneAlias.ps1 script runs, the following appears:

Count Name                      Group
----- ----                      -----
    6 Remove-Item               {del, erase, rd, ri...}
    3 Set-Location              {cd, chdir, sl}
    3 Get-History               {ghy, h, history}
    3 Get-ChildItem             {dir, gci, ls}
    3 Get-Content               {cat, gc, type}
    3 Move-Item                 {mi, move, mv}
    3 Copy-Item                 {copy, cp, cpi}

To see if an alias for the Measure-Object cmdlet exists already, you can use the Get-Alias cmdlet and the -definition parameter as shown here:

PS C:\> Get-Alias -Definition Measure-Object

CommandType     Name                                      Definition
-----------     ----                                      ----------
Alias           measure                                   Measure-Object

If you like the alias measure, you can use that alias. However, you may decide that the readability of the alias measure is hampered by the fact, that it only saves two key strokes. Because of the implementation of the tab expansion feature, all you need to do is type measure-o and press TAB. In general, when creating personal aliases, I prefer to sacrifice readability for ease of use. My very favorite aliases are one-letter and two-letter aliases. I use the one-letter aliases for commands I frequently use, because they are the most useful and most obscure. I use the two-letter aliases for most of my other alias needs. The two-letter combination can easily correspond to the verb noun naming convention. Therefore, mo would be a logical alias for the Measure-Object cmdlet. To ensure its availability, use the Get-Alias cmdlet as shown here:

PS C:\> Get-Alias -Name mo

How many two-letter aliases are there?

The two-letter alias namespace is rather large. How large? You have to take every letter in the range A-Z, and pair it with every other letter in the range of A-Z. If you are good with algebra then you immediately know there are 676 possible letter combinations (26 x 26). However, if your algebra is a bit rusty, you may wish to write a Windows PowerShell script to figure it out for you. The problem with such an approach is that we cannot use the range operator to produce a range of letters. It works with numbers, so 1..10 automatically creates a range of numbers with the values 1 through 10, and can save you a lot of typing. However, because we have ASCII numeric representations of the letters A-Z, you can use this technique to create a range of letters. The ASCII value 97 is the “A” character, and the ASCII value 122 is “Z”. After we have the numeric range, we use the ForEach-Object cmdlet and convert each letter to a character by using the [char] type. We store the resulting array of letters in the $letters variable. We then do two loops through the array and store the resulting letter combinations in the $lettercombination variable, which is constrained as an array by using the [array] type. The Measure-Object cmdlet is used to count the number of possible letter combinations. The ListTwoLetterCombinations.ps1 script is shown here.

ListTwoLetterCombinations.ps1


$letterCombinations = $null
$asciiNum = 97..122
$letters = $asciiNum | ForEach-Object { [char]$_ }
Foreach ($1letter in $letters)
{
 Foreach ($2letter in $letters)
 {
  [array]$letterCombinations += "$1letter$2letter"
 }
}
"There are " + ($letterCombinations | Measure-Object).count + " possible combinations"
"They are listed here: "
$letterCombinations

To create a new alias, you can use either the New-Alias cmdlet or the Set-Alias cmdlet. You could also use the New-Item cmdlet and target the Alias drive. The problem with that technique is it does not support the Description parameter, which allows you to specify additional information about the alias. The other problem with using the New-Item cmdlet to create an alias is that it is more typing. So as a best practice, I always use either the New-Alias or the Set-Alias cmdlet.

In choosing between the two cmdlets, which should you use when creating a new alias? Before we answer that, we should actually talk about what the cmdlets are intended to be used for. The New-Alias cmdlet, obviously creates a new alias. The Set-Alias cmdlet is used to modify an existing alias. If an alias does not exist, it will create the alias for you. Therefore, many people use the Set-Alias cmdlet to both create and modify an alias. There is a danger in this approach, however, in that you can inadvertently modify a previously existing alias with no notification. If this is your desired behavior, that approach is fine. A better approach is to use the New-Alias cmdlet when creating an alias. This allows you to specify the Description parameter, and to receive notification if an alias that you are trying to create already exists. To assign a description to an alias when creating it, you use the Description parameter as seen here:

New-Alias -Name mo -Value Measure-Object -Description "MrEd Alias"

In an enterprise-scripting environment, many companies like defining a corporate set of aliases—this provides for a consistent environment. A network administrator working on one machine can be assured that a particular alias is available. This also helps to ensure a predictable and consistent environment. By using the same value for the Description parameter of the alias, it is easy to list all the corporate aliases. To do this, you would filter the list of aliases by the description. An example of this is seen here:

PS C:\> Get-Alias | Where-Object { $_.description -eq 'mred alias' }

CommandType     Name                                      Definition
-----------     ----                                      ----------
Alias           mo                                        Measure-Object

When using the -eq operator in the code block of the Where-Object cmdlet, the filter is case insensitive. If you need a case sensitive operator, you would use -ceq. The “c” is added to all the operators to form a case sensitive form of the operator—by default the operators are case insensitive. As shown here, when using the case sensitive operator, the filter does not return any aliases:

PS C:\> Get-Alias | Where-Object { $_.description -ceq 'mred alias' }
PS C:\>

In addition to specifying the Description parameter, many companies also like to use the Option parameter to make the alias either read-only or constant. To make the alias read-only, you supply the readonly keyword to the Option parameter. This is shown here:

New-Alias -Name mo -Value Measure-Object -Description "MrEd Alias" -Option readonly

The advantage of making the alias read-only is that it offers protection against accidental modification or deletion. This is seen here:

Image of read-only alias protecting against modification or deletion


There is an additional advantage to making the alias read-only: It can be modified or deleted if needed. If you wish to modify the description, you use the Set-Alias cmdlet to specify the name, value the new description, and use the Force parameter. This is seen here:

Set-Alias -Name mo -value measure-object -Description "my alias" –Force

If you need to delete a read-only cmdlet, you use the Remove-Item cmdlet and specify the Force parameter. This is seen here:

Remove-Item Alias:\mo -Force

To create a constant alias, you use the constant keyword with the option parameter. This technique is shown here:

New-Alias -Name mo -Value Measure-Object -Description "MrEd Alias" -Option constant

As a best practice, you should not create constant aliases unless you are certain you do not need to either modify them or delete them. This is because a constant alias can be neither modified nor deleted—in effect, they really are constant. The error message is a bit misleading in that it says the alias is either read-only or constant, and it suggests attempting to use the Force parameter. The reason this is misleading is the message is displayed even when the command was run with the Force parameter. This error message is seen here:

Image of error message

 

Well, RR, this should get you started on using aliases in Windows PowerShell. Join us tomorrow as we continue examining ways to customize our Windows PowerShell environment.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (11/6/09)

Bookmark and Share

In this post:

 

Using the Active Directory DirectoryEntry Class

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been writing some scripts for Active Directory, and this past weekend I ran into something that puzzled me. When a search is performed against Active Directory (using the DirectoryEntry class and searcher class), and when the GetDirectoryEntry method is called against the returned SearchResult objects, the members of the newly returned DirectoryEntry objects do not match the members listed on MSDN at all (for the .NET class).

Why is that? The Windows PowerShell GetDirectoryEntry method seems to have bound directly to the Active Directory object, whereas the MSDN documentation speaks of returning a DirectoryEntry object with a number of methods and properties available to it. And is there a way to return a true instance of the .NET class in Windows PowerShell?

-- AD

Hey, Scripting Guy! AnswerHello AD,

Microsoft Scripting Guy Ed Wilson here. The DirectoryEntry class is documented on MSDN. To gain access to some of the methods and properties described in the MSDN article, you will need to access the base object, which is the .NET Framework class that was used to create the object we see in Windows PowerShell. In many cases, accessing the base object is not required. But because of the way that Active Directory Services Interface (ADSI) created it, we need to use the base object from time to time. An example of obtaining a DirectoryEntry object by using the ADSI type accelerator and displaying its base properties is seen here:

$de = [adsi]”LDAP://dc=nwtraders,dc=com”

$de.psbase | Get-Member

The base members of the DirectoryEntry object are seen here:

Image of base members of DirectoryEntry object


You can, of course, create a DirectoryEntry object directly as seen here. There are five different constructors available for this class. The different constructors are all documented on MSDN:

$de = new-object System.DirectoryServices.DirectoryEntry

You will still need to use PSBase to access all of the methods and properties, but the advantage of creating your own object is you get to define the constructor that is used. I have written several Hey, Scripting Guy! articles that talk about this.


 

How Can I Run a Script Whenever a New USB Drive Is Attached to a Computer?

Hey, Scripting Guy! Question

Hey, I need to run a script whenever a new USB drive is attached to a computer. Is there a trigger I can use to call my script?

-- JG

 

Hey, Scripting Guy! AnswerHello JG,

Yes. The Monitor Process Event script from the TechNet Script Center Gallery should help. You will need to change Win32_Process to Win32_LogicalDisk, and it will generate an event when a new logical drive is detected.  


Making a Specific Windows PowerShell 2.0 Script Work with Windows PowerShell 1.0

Hey, Scripting Guy! Question

Hey, Scripting Guy! In regards to the Can I Determine Which Folders Are Using the Most Space on My Computer? article, I appreciate the level of detail and helpfulness of this article. The script was written using Windows PowerShell 2.0. For those folks like me who are still using Windows PowerShell 1.0, how can such a task be done?

-- JS


Hey, Scripting Guy! AnswerHello JS,

Basically the only Windows PowerShell 2.0 things I did in that script was use the new help tags. When they are removed, the script will work. I took the time to do this for you, and to test it on my Windows XP box that runs Windows PowerShell 1.0. I then posted the script to the new Script Center Script Gallery. You can access the script from http://bit.ly/14dzcY.


 

Can I Use VBScript to Distinguish Between Versions of Windows Server 2008?

Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I make the distinction between a 32-bit and a 64-bit version of Windows Server 2008 using VBScript?

-- VJ


 

Hey, Scripting Guy! AnswerHello VJ,

Here is some Windows PowerShell code I wrote to determine if a computer is 32-bit or if it is 64-bit.

Function Test-64

{ If($env:PROCESSOR_ARCHITECTURE -match '64') { $true } ELSE { $false }}

 

Function Test-32

{ if($env:PROCESSOR_ARCHITECTURE -match '86') { $true } ELSE { $false} }

 

In the two test functions, I read the value of the Processor_Architecture environmental variable. Here is a VBScript that you can modify to search for the Processor_Architecture variable to use in your VBScript code.

 

 

How Can I Determine the Drive Letter of My CD-ROM Drive?

Hey, Scripting Guy! Question

Hey Scripting Guy! The script in the How Can I Determine the Drive Letter for the Drive My Script is Currently Running On? article does not return the CD drive letter (like it says it does) but rather the "C:\" drive every time, no matter which PC I put the CD into. Any idea why? 

-- JW

 

Hey, Scripting Guy! AnswerHello JW,

The script that you refer to does not return the drive of the CD-ROM necessarily. It returns the drive letter from where the script was launched. In the email from RKG (in the article), his script was on a CD-ROM and he wanted to know the drive letter from where the script was running—in his case the CD-ROM. If you are running the script from your C: drive (as would often be the case), the script will always return the drive letter “C”. If you are running it from your D: drive, the script will always return “D”. If you have it on a portable USB drive, and do not know which drive letter has been assigned to your USB drive, the script will convey this information to you. This is in fact the purpose of the script: to tell you from where the script is being launched. It is useful in situations when, for instance, you have the script on a USB drive, and want to copy files from your computer to the USB drive, but you do not know the letter of the USB drive. You would include this code at the top of your script to obtain the drive letter of the USB drive, and then begin your copy operation. For simply determining the drive letter of your CD-ROM drive, you could use the List CD-ROM Properties script from the new TechNet Script Center Gallery. 

 

Well, this concludes another edition of Quick-Hits Friday. It also concludes another exciting week on the Script Center. Join us next week as we delve into the mysteries of…we’ll let that remain a mystery for now.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace. 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Can I Start an Event Based on When a Registry Value Is Changed?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I need to be notified when a particular registry key value gets changed. The registry value is in the HKEY_LOCAL_MACHINE hive. What I am trying to accomplish is this: I am using a script to install software. After the first piece of software is successfully installed, I want to install a subsequent piece of software. And after this is done, I will reboot the computer. I have tested this technique in our lab, and I am able to postpone the reboot. I have tried pausing the execution of the install script by using the Start-Sleep cmdlet, and this works okay. The problem is that on fast computers the pause is too long, and on slow computers the pause is not long enough. It seems that I cannot figure out a really good way to do this.

I am thinking that if I cannot figure this out, I will need to just record the computers on which the install fails, and manually visit each machine; however, several of the computers are at remote locations and this is not a really practical solution. The idea for the registry key is that each software package creates its own registry key, and if I can detect when this occurs, I can then start the next install. Can you help me?

-- LK

Hey, Scripting Guy! AnswerHello LK,

Microsoft Scripting Guy Ed Wilson here. I am listening to the The Kingston Trio on my Zune as I check the scripter@microsoft.com e-mail inbox. I am sipping a cup of Constant Comment tea and munching on an ANZAC biscuit. The weather outside is frightful, but inside my office it is delightful. And since I’ve no place to go, let it snow!

By the way, Craig and I have been having several discussions about the 2010 Scripting Games. One thing we can tell you, they will not be held in the summer. They will be earlier! I have been busy working on events. If you have any ideas for the 2010 Scripting Games, e-mail us at scripter@microsoft.com. What should you say? Just about anything. We are interested in ideas for events, ideas to make the Games go better, ideas to increase participation—just about anything.

LK, I wrote the MonitorRegistryKeyValueChangeEvent.ps1 for you. The MonitorRegistryKeyValueChangeEvent.ps1 script watches a registry key value, and when that value changes, it fires an event. To use the script, you must have Windows PowerShell 2.0 installed. You will need to modify the $keyPath variable and the $valueName variable to use it. The complete MonitorRegistryKeyValueChangeEvent.ps1 script is seen here.

MonitorRegistryKeyValueChangeEvent.ps1

#Requires –version 2.0

$hive = "HKEY_LOCAL_MACHINE"

$keyPath = "Software\\Microsoft\\WBEM\\Scripting"

$valueName = "Default NameSpace"

$gciPath = $keyPath -replace "\\","\"

$query = "Select * from RegistryValueChangeEvent where Hive='$hive' AND " +

         "KeyPath='$keyPath' AND ValueName='$ValueName'"

Register-WmiEvent -Query $query -SourceIdentifier KeyChanged

Wait-Event -SourceIdentifier KeyChanged

"New value for $valueName is " + (Get-ItemProperty -Path HKLM:\$gciPath).$valueName

The MonitorRegistryKeyValueChangeEvent.ps1 actually begins with a comment that states the script requires version 2.0. This is not really a comment; it is a directive to Windows PowerShell that will prevent script execution on a Windows PowerShell 1.0 computer. Of course the script will not execute on Windows PowerShell 1.0, but this directive will save you time and generate a more readable error message. The #Requires statement is seen here:

#Requires -version 2.0

The $hive variable holds the registry hive that will be monitored. In this script, you monitor the HKEY_LOCAL_MACHINE hive. This is the only hive the RegistryKeyChangeEvent WMI class supports. Any of the WMI classes that are derived from the RegistryEvent abstract class only work on the HKEY_LOCAL_MACHINE hive. The WMI registry event classes are seen in Table 1.

Table 1  WMI Registry Event Classes

Event class

Hive location

Description

RegistryEvent

N/A Abstract

Base class for changes in the registry.

RegistryTreeChangeEvent

RootPath

Monitors changes to a hierarchy of keys.

RegistryKeyChangeEvent

KeyPath

Monitors changes to a single key.

RegistryValueChangeEvent

ValueName

Monitors changes to a single value.

 

The value of the registry hive, HKEY_LOCAL_MACHINE, is normally listed as all capital letters, and in some instances, this is actually a requirement. However, with the RegsitryKeyChangeEvent WMI class as used in Windows PowerShell, this is not case-sensitive. You could easily use all lowercase letters for your hive name and it would work fine. Keep in mind that though it is not case sensitive, you must spell it correctly for the script to work:

$hive = "HKEY_LOCAL_MACHINE"

The key path points to the registry key you wish to monitor. There are two things that you need to remember. The first is that you do not have a backward slash at the beginning of the string. The second thing to remember is that all of the backward slashes are escaped—you have double backward slashes between portions of the registry key name. The value assignment to the $keyPath variable is seen here:

$keyPath = "Software\\Microsoft\\WBEM\\Scripting"

The last portion of the path to the registry value you want to monitor is the value name itself. The variable $valueName stores the name of the value to monitor. This is shown here:

$valueName = "Default NameSpace"

The path to the registry value that is being monitored is seen here:

Image of the path to the registry value being monitored


After the path to the registry key value has been assigned via the three different variables, the replace operator is used to replace the double backward slashes with a single backward slash. This is only necessary for the value stored in the $keyPath variable because the hive and the value strings do not have any backward slashes in them. The resulting path is stored in the $gipPath variable. This path will be used with the Get-ItemProperty cmdlet to retrieve the new value that is assigned to the registry value—and the Get-ItemProperty cmdlet does not need to have the backward slashes escaped the way WMI does:

$gipPath = $keyPath -replace "\\","\"

The WMI query is the same as the WMI query that is used in VBScript. An example of monitoring the registry from VBScript can be seen in the Hey, Scripting Guy! article, How Can I Monitor Changes to a Registry Key?

When writing your VBScripts, if you keep your query separated from the WMI moniker, it makes it easier to reuse your WMI queries in a Windows PowerShell script.

$query = "Select * from RegistryValueChangeEvent where Hive='$hive' AND " +

         "KeyPath='$keyPath' AND ValueName='$ValueName'"

Two cmdlets are used to provide access to the WMI event system. The first cmdlet is the Registry-WmiEvent cmdlet. The WMI query that is stored in the $query variable is used, and the SourceIdentifier KeyChanged is assigned to the event. Only one event query can be assigned the SourceIdentifier of KeyChanged because SourceIdentifiers must be unique. If you inadvertently run the script a second time, this error appears:

Image of error that appears when inadvertently running script a second time

The Wait-Event cmdlet is used to pause the script execution until the event identified by the SourceIdentifier occurs. These two lines of code are seen here:

Register-WmiEvent -Query $query -SourceIdentifier KeyChanged

Wait-Event -SourceIdentifier KeyChanged

When the event triggers, the script continues to the next line. The Get-ItemProperty cmdlet is used to retrieve the new value that caused the event to trigger. This is seen here:

"New value for $valueName is " + (Get-ItemProperty -Path HKLM:\$gipPath).$valueName

After you run the script, the information seen in the following image appears in the Windows PowerShell ISE:

Information appearing in Windows PowerShel ISE after running script

Well, LK, that is all there is to monitoring the registry for a key value change event. Join us tomorrow as we dive into the virtual mail bag and retrieve questions that require shorter answers. Yes, it is time for Quick-Hits Friday.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

More Posts Next page »
 
Page view tracker