Quantcast
Channel: Hey, Scripting Guy! Blog
Viewing all 3333 articles
Browse latest View live

Easily Filter Empty WMI Properties in PowerShell

$
0
0

Summary: Microsoft Scripting Guy Ed Wilson introduces a WMI filter to remove empty WMI properties by using Windows PowerShell.

 

Microsoft Scripting Guy Ed Wilson here. It is no secret by now that I love Windows Management Instrumentation (WMI). The information I can obtain and the things I can do with it are astounding. In the Windows PowerShell class I was teaching last week in Montreal, I told the class that I think when people say they “do not like WMI,” it is because they are still hampered by nightmares from their experiences of trying to use WMI back in the VBScript days.

Note   This is part four of a multipart article on developing a WMI helper module. On Monday, I created the base WMI module, and included a couple of really great functions that query the WMI schema and return WMI class methods and properties. On Tuesday, I added a function that returns the key property of a WMI class. This is useful when working with WMI instance methods. On Wednesday, I created a function that will return the values of those key properties. Today, I add a HasWMIValue filter to the module.

Another Note   The module for today’s Hey, Scripting Guy! Blog post is on the Scripting Guys Script Repository.

For example, here is a really simple example. I want to obtain information about the BIOS on my computer. Here is the VBScript to do so (copied from the Scripting Guys Script Repository).

VBScript to Get the BIOS information from a local computer

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _

    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

 

Set colBIOS = objWMIService.ExecQuery _

    ("Select * from Win32_BIOS")

 

For each objBIOS in colBIOS

    Wscript.Echo "Build Number: " & objBIOS.BuildNumber

    Wscript.Echo "Current Language: " & objBIOS.CurrentLanguage

    Wscript.Echo "Installable Languages: " & objBIOS.InstallableLanguages

    Wscript.Echo "Manufacturer: " & objBIOS.Manufacturer

    Wscript.Echo "Name: " & objBIOS.Name

    Wscript.Echo "Primary BIOS: " & objBIOS.PrimaryBIOS

    Wscript.Echo "Release Date: " & objBIOS.ReleaseDate

    Wscript.Echo "Serial Number: " & objBIOS.SerialNumber

    Wscript.Echo "SMBIOS Version: " & objBIOS.SMBIOSBIOSVersion

    Wscript.Echo "SMBIOS Major Version: " & objBIOS.SMBIOSMajorVersion

    Wscript.Echo "SMBIOS Minor Version: " & objBIOS.SMBIOSMinorVersion

    Wscript.Echo "SMBIOS Present: " & objBIOS.SMBIOSPresent

    Wscript.Echo "Status: " & objBIOS.Status

    Wscript.Echo "Version: " & objBIOS.Version

    For i = 0 to Ubound(objBIOS.BiosCharacteristics)

        Wscript.Echo "BIOS Characteristics: " & _

            objBIOS.BiosCharacteristics(i)

    Next

Next

 

Windows PowerShell script to retrieve the BIOS information from a local computer

Get-WmiObject win32_bios | format-list *

I am not kidding! It is one short line. In fact, if I use aliases, the command becomes even shorter. Here is the “short” version of the command. The short version is 18 characters long; the VBScript to do exactly the same thing is more than 18 lines long:

gwmi win32_bios | fl *

The output from the Windows PowerShell Get-WMIObject command is shown in the following figure.

Image of output from Get-WMIObject command

With power like this at one’s fingertips, it is hard not to fall in love with WMI and Windows PowerShell. One thing that does annoy me, a bit, is the fact that nearly any query to WMI results in many lines of property names and no values. This was especially distressing in the old VBScript days because it was so much work to retrieve a single property value that I really felt disappointed when the command did not return any information.

To solve the problem of WMI classes that do not populate values in all of the class properties, I have a filter that I use. In fact, when I was writing the scripts for the Windows 7 Inside Out book and the Windows 7 Resource Kit, I used this filter extensively. I decided it is just the sort of thing I want to have constantly available. I use WMI on a daily basis, and therefore piping the output to the filter really makes sense. To make it always available, I modified it a bit and placed it in my WMI module. I uploaded HSGWMIModuleV4 to the Scripting Guys Script Repository. I have comment-based help, but there is no need to go over that. The key portion of the filter is shown here:

$_.properties |

   foreach-object -BEGIN {write-host -ForegroundColor BLUE $_.path} -Process {

     If($_.value -AND $_.name -notmatch "__")

      {

        @{ $($_.name) = $($_.value) }

      } #end if

    } #end foreach property

 

In the filter, the first thing I do is look at the input object that is piped to the filter, and retrieve a collection of the properties. I pipe the properties to the Foreach-Object cmdlet:

$_.properties |

The first thing I do in the Foreach-Object cmdlet is use the begin parameter to print out the path to WMI instance, as shown here:

foreach-object -BEGIN {write-host -ForegroundColor BLUE $_.path}

In the process portion, I check to see if the property has a value, and if it does not match an underscore. This removes some system properties, and it also removes properties that do not have a value. I create a hash table from the properties and their associated values, and return it. This portion of the code is shown here:

-Process {

     If($_.value -AND $_.name -notmatch "__")

      {

        @{ $($_.name) = $($_.value) }

      } #end if

To use the HasWMiValue filter, I import my WMI module, and then pipe the results of a Get-WMiObject command to it. This is shown here:

Import-Module HSG*wmi*4

GWMI win32_bios | HasWmiValue

I removed the HSGWMIModuleV4 and did a query of the Win32_bios WMI class while listing all the properties. There are a number of properties from the Win32_Bios class that do not return values. I then import the HSSGWMIModuleV4 and instead of piping the management into Format-List, I pipe it to the HasWMiValue filter. These two commands and their associated output are shown in the following figure.

Image of two commands and associated output

The HasWMIValue filter works great. It lists all of the properties of a WMI class that contain values, and suppresses the system properties as well as the properties that are empty. This output is great for exploring WMI.

Once imported, there is no need to import the WMI module a second time. I can use the HasWmiValue directly in the pipeline. I uploaded the complete WMI helper module to the Scripting Guys Script Repository.

 

Well, that is about all there is to the HasWmiValue filter. I have comment-based help in the function, so if you need additional help, use Get-Help. Until tomorrow, keep on scripting!

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 


Query WMI Classes by Using PowerShell and Wildcard Patterns

$
0
0

Summary: Learn how to query WMI classes using Windows PowerShell and wildcard character patterns by using a free Scripting Guy module.

 

Microsoft Scripting Guy Ed Wilson here. Well, the Scripting Wife and I are finally settling down to a normal routine here in Charlotte, North Carolina, in the United States. Of course, that settling down will not last too long because next week we head out to California. I am speaking at a Microsoft internal conference for the premier field engineers (PFE) at a conference called Geek Week (Geek Ready). I am making two presentations to each of the three sessions, as well as sitting on a panel discussion. In addition, for session 1 I am having a special side meeting with the PFE Windows PowerShell special interest group. It will be lots of fun. Following that week, we head up to Orange County, California, where I will be conducting a special five day Windows PowerShell workshop for Microsoft Premier customers. So we will spend two weeks in sunny Southern California. Ah, the life of someone who knows Windows PowerShell.

Anyway, I do need to mention something that happened to me on Monday. I changed the BIOS settings on my new laptop, and when I saved the changes, BitLocker Recovery Password Viewer for Active Directory Users and Computers. A quick call to the help desk, and I was back up and running. It was cool!

I am continuing to work on my HSGWMIModule and today I am up to version 5.

Note   This is part five of a multipart article on developing a WMI helper module. On Monday, I created the base WMI module, and included a couple of really great functions that query the WMI schema and return WMI class methods and properties. On Tuesday, I added a function that returns the key property of a WMI class. This is useful when working with WMI instance methods. On Wednesday, I created a function that will return the values of those key properties. Yesterday, I added a HasWMIValue filter to the module. Today, I add two functions. The first is a function that will query WMI classes using a wildcard character pattern. The second function is an Out-TempFile function that accepts piped input and displays results in a temporary text file in Notepad.  

Anoter Note   The module for today’s blog post is on the Copy-Modules function from my Windows PowerShell ISE module. Using this technique, all you need to do is go to the Scripting Guys Script Repository, copy the module to the Clipboard, save it in your scratch directory, and use the Copy-Modules function to copy it to your modules folder. If your scratch directory is c:\fso, it is completely automatic. If it is not, I recommend you edit the Copy-Modules function to use your scratch directory, which makes it really easy to use. The command and associated output are shown in the following figure.

Image of command and associated output

When the HSGWMIModuleV5 is installed, all of the functions and filters contained within the module become available when I import the module. I can do this in either the Windows PowerShell console or the Windows PowerShell ISE. I am going to use the Windows PowerShell console for now. I do not need to type the entire module name because the Import-Module cmdlet accepts wildcard characters. After I import the module for the first time, I like to see which commands the module supports, so I use the Get-Command cmdlet to display the commands. The two commands are shown here:

Import-Module hsg*5

Get-Command -Module hsg*5

The two commands and their associated output are shown in the following figure.

Image of two commands and associated output

The Get-WmiClassesAndQuery function accepts wildcard characters for the WMI class name. It then searches for WMI classes that match the pattern. Upon finding the classes, it determines which WMI classes are not abstract and it queries them. The portion of the function that returns a list of WMI classes that match a wildcard character pattern is shown here (supply the wildcard character pattern to the $class variable):

Get-WmiObject -List $class -Namespace $namespace -ComputerName $computer

I use the same logic to determine dynamic classes I used in other functions in the HSGWMIModule*. I examine the class qualifiers to find the abstract qualifier. If the abstract qualifier appears, I skip the class. Here is that portion of the logic:

ForEach-Object {

   $abstract = $false

   [wmiclass]$class = "\\{0}\{1}:{2}" -f $computer,$namespace,$_.name

  Foreach($q in $_.Qualifiers)

   { if ($q.name -eq 'Abstract') {$abstract = $true} }

  If(!$abstract)

After all that work, it is a straightforward WMI query using the Get-WMIObject cmdlet. I used parameter decorations to make the class parameter mandatory in the first position, and accept pipeline input. Here is that parameter attribute:

[Parameter(Mandatory=$true,Position=0,valueFromPipeline=$true)]

I can use the function by directly passing a wildcard character to it, or I can use the parameters. These techniques are shown here:

Get-WmiClassesAndQuery *disk*

Get-WmiClassesAndQuery -class *disk* -namespace root\wmi

To run a query remotely you must have administrator rights. If you run the command with alternate credentials, you will need to supply the path to the module when you import it because the Copy-Modules function installs into the current user profile location. Of course, if you run the Windows PowerShell ISE with alternate credentials when you use the Copy-Modules function to perform the installation, you will already have the module in your alternate profile. The workaround is to manually install the module in the all users/all hosts profile location.

The Get-WmiClassesAndQuery function really shines when combined with the HasWmiValue filter and the Out-TempFile function. The command is shown here:

Get-WmiClassesAndQuery *adapter* | HasWmiValue | Out-TempFile

The previous command queries every dynamic WMI class that contains the pattern adapter in the name. This includes Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration as well as an associative class. The HasWmiValue greatly reduces the amount of information that is displayed by removing empty properties, and the Out-TempFile function makes it easy to peruse the returned data. The temp file is shown in the following figure.

Image of temp file

 

That is all there is to using the HSGWmiModuleV5. WMI Week will continue tomorrow when I will add some aliases and other things to the HSGWmiModule. Until tomorrow!

I invite you to follow me on Facebook. If you have any questions, send email to me at Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Modify PowerShell Comment-Based Help to Display Function Use

$
0
0

Summary: In the final version of my Windows PowerShell WMI helper module, I add aliases and modify comment-based help to find roles of functions.

Microsoft Scripting Guy Ed Wilson here. It is the weekend, at least in Charlotte, North Carolina, in the United States. I have had a lot of fun working on my WMI helper module this week, and the last thing I need to do is to add a few aliases, and add the function description attribute using a technique I recently came up with. Today, I am up to version 6 on my HSGWMIModule module.

Note This is part six of a multipart article on developing a WMI helper module. On Monday, I created the base WMI module, and included a couple of really great functions that query the WMI schema and return WMI class methods and properties. On Tuesday, I added a function that returns the key property of a WMI class. This is useful when working with WMI instance methods. On Wednesday, I created a function that will return the values of those key properties. On Thursday, I added a HasWMIValue filter to the module. Yesterday, I added two functions. The first is a function that will query WMI classes using a wildcard character pattern. The second function is an Out-TempFile function that accepts piped input and displays results in a temporary text file in Notepad.

Another Note The version six of my HSGWMIModule is available on the Scripting Guys Script Repository.

On May 28, 2011, I wrote a Hey, Scripting Guy! Blog post called Learn How to Use Description Attributes in PowerShell Functions in which I talked about using the rather finicky [system.compomentmodel.description] attribute. It does not play well with comment-based help, and it has a tendency to error out for no apparent reason. In that article, I also talk about finding functions that come from various modules, so the article is useful.

On the other hand, I was not extremely happy with the solution. I was playing around looking at the members of a function, and I noticed something—there is a description property; the same property exists for variables, aliases, and other things. The description property of a function appears in the following figure.

Image of description property of a function

The difference is that when creating a new variable with the New-Variable cmdlet or the New-Alias cmdlet, the description parameter is directly exposed. I have not figured out a way to do this for a function—until now. In the same way that I create a function and then add an alias, I can create a function and then add a description to it. The secret is to use the Get-Command cmdlet to return a functioninfo object; from the functioninfo object, I can directly add the description to the function. The command to add a description to a function is shown here:

(Get-Command testfunction).description = "my mred function"

In the following figure, I create a function, and then assign a description to it. Once I have done that, I use the Get-Item cmdlet and pipe the results to the Format-List to display the newly added description.

Image of creating function and assigning description

Okay, enough theory. I want to create aliases for all of the functions exposed by my HSGWMImoduleV5 (I will add the aliases and the function descriptions in HSGWMImoduleV6). The first thing I need to do is find out which functions reside in the module. To do that, I import the module, and then use the Get-Command cmdlet to retrieve the functions. These two commands and their associated output are shown here:

PS C:\Users\ed.IAMMRED> Import-Module hsg*5

PS C:\Users\ed.IAMMRED> Get-Command -Module hsg*5

 

CommandType              Name                                       Definition

Function                        Get-WmiClassesAndQuery           ...

Function                        Get-WMIClassesWithQualifiers     ...

Function                        Get-WmiClassMethods                ...

Function                        Get-WmiClassProperties              ...

Function                        Get-WmiKey                              ...

Function                        Get-WmiKeyvalue                       ...

Filter                             HasWmiValue                            ...

Function                        New-Underline                          ...

Function                        Out-TempFile                             begin {...

One thing that makes it easier to add the aliases and the function descriptions is to load the HSGWMImoduleV5 via the Import-Module cmdlet into the Windows PowerShell ISE. This simple action permits me to take advantage of tab expansion, which is a good thing given the length of some of the WMI function names. However, the long names also take advantage of one of my Windows PowerShell best practices of using a descriptive name for the function and shortening the name with an alias.

After the changes are made to the new HSGWMImoduleV6, I save it to my working directory. Next, I remove the HSGWMImoduleV5 from memory by using the Remove-Module cmdlet as shown here:

Remove-Module hsg*

I peruse the function: drive to ensure that all of my WMI functions successfully unloaded. To do this, I use dir command (alias for the Get-Childitem cmdlet) on the function drive. This command is shown here:

dir function:

Now I import my newly revised HSGWMImoduleV6.

I decided to use two fields in comment-based help that are not used by any cmdlets. I found these by examining the results from Get-Help by piping it to Get-Member. This command is shown in the following figure.

Image of piping Get-Help results to Get-Member

By adding the two attributes, I can use Get-Help to filter out my functions. First, I can filter out all of my functions by using Get-Help:

Get-Help * | ? {$_.role}

To find only the functions that work with WMI metadata, I use the value meta in the syntax that is shown here:

Get-Help * | ? {$_.role -match 'meta'}

To find the helper functions, I use the value helper in the following command:

Get-Help * | ? {$_.role -match 'helper'}

To find the query commands, I use this query:

Get-Help * | ? {$_.role -match 'query'}

The commands and associated output are shown in the following figure.

Image of commands and associated output

The syntax of one of these comment-based help functions will suffice for you to get the idea. Here are the two new parameters I added to the help:

.Role

Meta

.Component

HSGWMIModuleV6

I placed these beneath the parameter section and above the notes section. This is shown in the following figure.

Image of two new parameters beneath parameter section

I created aliases for all the commands in the module. To find all the aliases, use this command:

Get-Command -Module hsg*6 -CommandType function | % {Get-Alias -Definition $_.name}

That’s it for my WMI Helper module. I have uploaded version 6 to the Scripting Guys Script Repository. If there are other things you would like to see in my module, let me know via email at scripter@microsoft.com.

WMI Week will continue tomorrow when I will talk about an easy way to use WMI association classes.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

Use WMI Association Classes to Relate Two Classes Together

$
0
0

Summary: Use a cool Windows PowerShell technique to easily relate two WMI classes together.

 

Microsoft Scripting Guy Ed Wilson here. Well, the Scripting Wife and I are getting settled back down from our Canadian tour. We had a great time visiting MVPs on our return. We got to see Jeffrey Hicks in Syracuse, Joel Bennett in Rochester, and Ken McPherson in Pittsburgh, Pennsylvania. While teaching my Windows PowerShell class in Montreal, I realized that I had not written very much about using Windows PowerShell with WMI association classes.

When querying a WMI association class, the results return references to external classes. In fact, the things that are returned point to the key property of the referenced classes. An example will help to clarify this. My laptop has a network adapter. The win32_networkadapter WMI class describes the maker, the speed, MAC address, and other information about my network adapter. The following query returns information about my network adapter:

gwmi win32_networkadapter -Filter "netconnectionid = 'local area connection'"

If I want to find all of the information about my network adapter, but I do not want to see any empty properties (because some WMI classes contain literally hundreds of properties many of which do not return any information), I use the haswmivalue filter from my WMI module.

In the following figure, I use the haswmivalue filter from my HSGWMImoduleV5 module to return all the properties that contain a value. (Of course, the haswmivalue function also exists in HSGWMImoduleV6 as well. Version six of my HSG WMI Module is the latest version, and it includes several upgrades to version five).

Image of using haswmivalue filter

In addition to having physical information, such as the MAC address and speed, a network adapter also has a protocol (or more than one protocol) bound to it. To find protocol type of information, such as the TCP/IP address, subnet mask, and default gateway, it is necessary to query the Win32_NetworkAdapterConfiguration WMI class. To make matters worse, the netconnectionid property that I used when querying the Win32_NetworkAdapter WMI class is not available. In addition, the key property from Win32_NetworkAdapter is DeviceID, but the key property from Win32_NetworkAdapterConfiguration is index. Upon closer observation, however, this problem is not as bad as it might seem. Using my Get-WmiKeyValue function from my HSGWmiModuleV6 WMI module, I can easily see that the properties from the two different WMI classes are probably related (the values seem to be identical). This is shown in the following figure.

Image showing that properties from the two WMI classes are probably related

From my previous query of Win32_NetworkAdapter, I saw that the deviceID of 7 was the network interface from which I wanted to obtain information. Using this value as the index number, I come up with the following query (gwmi is an alias for Get-WmiObject). The first position is class and I left it out. The f stands for filter and I was able to use the abbreviation. The haswmivalue filter is from my WMI module.

gwmi win32_networkadapterconfiguration -F "index = 7" | HasWmiValue

The command and associated output are shown in the following figure.

Image of command and associated output

What is needed is a way to query both WMI classes at the same time. I need to see both the hardware information and the protocol related information for the same adapter. There is a solution for this problem; I can use an association WMI class. In the old VBScript days, querying a WMI association class was a really hard-core task that few outside of the Microsoft Scripting Guys really understood. This can all change in the Windows PowerShell world, because querying WMI association classes is trivial. Keep in mind that there are more than 100 association classes in root\cimv2 alone. These classes expose very useful and powerful information about your system.

Using my Get-WmiClassesWithQualifiers function from my WMI module, I can easily find association classes in any WMI namespace. For our purposes, I will focus on association classes in Root\CimV2 (the default WMI namespace.) I am only interested in working with dynamic WMI classes, and unfortunately, I did not add a dynamic filter to my Get-WmiClassesWithQualifiers function (this is something I will probably do at a later date). In the meantime, I can reduce the number of WMI classes I need to filter through by removing the classes that begin with cim. In most cases, classes that begin with cim are abstract classes. I use the following query to find association classes in Root\CimV2.

Get-WMIClassesWithQualifiers -qualifier association | ? { $_ -notmatch '^cim'} | sort

The command and associated output are shown in the following figure.

Image of command and associated output

Exploration of these association classes will yield fruitful and profitable results. To illustrate working with these classes, I want to examine the Win32_NetworkAdapterSetting association class. This class relates the two WMI classes we began with: Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration.

The first thing I do is query the Win32_NetworkAdapterSetting WMI class directly by using the Get-WMIObject cmdlet. This query is shown here (using the gwmi alias for Get-WmiObject):

gwmi win32_networkadaptersetting

The command and associated output are shown in the following figure.

Image of command and associated output

A close examination of both the element and the setting properties reveals they point to the key properties of the Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration WMI classes. It is possible to filter out using WQL, but it starts to get tricky, and it is therefore easier to use the Where-Object cmdlet to find an element that has the deviceID of 7 (the value we found earlier). This is really easy to do, and the command is shown here (I use gwmi for Get-WmiObject and ? for Where-Alias):

gwmi win32_networkadaptersetting | ? {$_.element -match 7}

The [WMI] management accelerator accepts this fully qualified path to a management object, and will return the associated class. To query these, I first store the result from the previous command into a variable called $adapter. Next, I use the [WMI] type accelerator to return the specific WMI instance of my Win32_NetworkAdapter and my Win32_NetworkAdapterConfiguration classes. Here are the three commands:

$adapter = gwmi win32_networkadaptersetting | ? {$_.element -match 7}

[wmi]$adapter.Element | HasWmiValue

[wmi]$adapter.Setting | HasWmiValue

The following figure illustrates these three commands and the associated output.

Image of three commands and associated output

One other thing before I go. It is possible to perform a less direct filter. This is because using the [WMI] type accelerator and the path, I obtain a complete instance of the WMI class that is referenced. This means I can use the same type of query that I used previously with Get-WMIObject when querying the WMI classes directly. Perhaps an example will suffice. In the following code, I use the Get-WMIObject (gwmi is the alias) to query the Win32_NetworkAdapterSetting WMI class. I use the where-object cmdlet (? is the alias) to perform my filtering. I already know that the first command, gwmi win32_networkadaptersetting, returns a collection of paths that point to the key values of all instances of Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration. I choose the value stored in the element property because it points to instances of Win32_NetworkAdapter. I can therefore use one of my favorite techniques that I call “group and dot” where I group the results by using parentheses, and I access a specific property by using dotted notation. After I have the specific property, I look for elements that have a value of the netconnectionid property that will match local area connection. The command is shown here:

gwmi win32_networkadaptersetting | ? {([wmi]$_.element).netconnectionID -match 'local area connection'}

The command and associated output are shown in the following figure.

Image of command and associated output

Well, that is all there is to working with WMI association classes. Hopefully, you will search for other association classes using the techniques I have outlined here, and then you will also begin to explore the type of information that is available.

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Append CSV Files Easily

$
0
0

Summary: Learn how to use Windows PowerShell to easily append one CSV file to another CSV file.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a problem, and I have searched everywhere on the Internet to find an answer. I need to be able to append one comma separated value (CSV) to another CSV file and create a new CSV file. I know that I can use Get-Content and Out-File cmdlet to sort of do this, but the problem is that I end up with the header information from the files getting in the way. I have to then go in and edit the resulting file, and I would really like to automate this process if I could. I would LOVE a script to do this. Please help me; I really need to get this done.

—RH

 

Hey, Scripting Guy! AnswerHello RH,

Microsoft Scripting Guy Ed Wilson here. Ever since the birth of XML, people have been proclaiming the end of CSV files. The simple fact of the matter is, however, that CSV files are convenient ways to store and represent data. The other simple fact is that Windows PowerShell makes using CSV files super easy. In my scratch directory, I have a few CSV files that contain information about users. These files appear in the following figure.

Image of CSV files containing information about users

Those CSV files contain information that might be needed when creating a new user: it has the first name, last name, primary group assignment, and the organizational unit (OU) where their account will be created. One of those CSV files is shown in the following figure.

Image of one of the CSV files

I decide to use a simple filter with the Get-ChildItem cmdlet to find only my user CSV files. Unfortunately, as seen here, the filter does not appear to correctly honor the meaning of a single ? wildcard character, and I end up with an additional CSV file that does not contain a letter following the word users. The command and associated output are shown here:

PS C:\> dir c:\fso -Filter users?.csv 

 

    Directory: C:\fso 

 

Mode               LastWriteTime                            Length Name

-a---                 12/7/2007   3:14 PM                   155 users.csv

-a---                 10/28/2011   2:24 PM                 272 UsersA.CSV

-a---                 10/28/2011   2:27 PM                 300 UsersB.csv

-a---                 10/28/2011   2:31 PM                 296 UsersC.csv

 

This is no problem. I decide to modify the filter to return only CSV files, and use the Where-Object cmdlet to look for files that begin with the word user and have a single character following the word. I use the wildcard character pattern users? and the like operator. I could just as easily have used a regular expression pattern and the match operator. The revised command and output are shown here:

PS C:\> dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'}

 

    Directory: C:\fso

 

Mode               LastWriteTime                            Length Name

-a---                 10/28/2011   2:24 PM                 272 UsersA.CSV

-a---                 10/28/2011   2:27 PM                 300 UsersB.csv

-a---                 10/28/2011   2:31 PM                 296 UsersC.csv

Now that I know I can find only the CSV files I need, I use the Import-CSV cmdlet to import all the CSV files and display the output. This command is shown here (dir is an alias for Get-ChildItem, and ? is an alias for the Where-Object cmdlet):

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Import-Csv

The command and associated output are shown in the following figure.

Image of command and associated output

Before I decide to throw the output to a new CSV file, I can sort the consolidated information. For example, I might want to sort by last name and first name. Here is the command that does that:

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Import-Csv | sort lname,fname

The command and associated output appear are shown in the following figure (note that it looks like I might have a duplicate user, and should therefore check with the personnel office to see if they really hired two Adam Barrs on the same day).

Image of command and associated output

I decide to see if there is an alias for Import-CSV because my command appears to be getting a little long. I use the Get-Alias cmdlet and look for a definition of Import-CSV. As shown here, I found the alias ipcsv:

PS C:\> Get-Alias -Definition import-csv

 

CommandType              Name                           Definition

Alias                              ipcsv                             Import-Csv

I also decide to do other sorts, such as sort by group and sort by OU. These commands are shown here:

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Ipcsv | sort group

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Ipcsv | sort OU

I decide to stick with the last name (lname column) and first name (fname column) sort, and I use the Export-CSV cmdlet to export my consolidated listing. I use the NoTypeInformation switch to keep Windows PowerShell from adding information to my CSV file that I do not need. Here is the command I ended up using. This is a one-line command that has wrapped. I did not add any line-continuation characters or anything else to the command. Dir is the alias for Get-ChildItem; ? is the alias for Where-Object; and sort is an alias for Sort-Object.

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Import-Csv | sort lname,fname | Export-Csv -Path c:\fso\UsersConsolidated.csv -NoTypeInformation

The command produces no output to the Windows PowerShell console:

Image showing command produces no output

The consolidated CSV file is shown in the following figure (a CSV file directly opens up in Microsoft Excel).

Image of consolidated CSV file

The only “bad” thing is my nice clean CSV files now have each element surrounded with quotation marks. Normally, this does not make any difference. For example, when reading the file with Import-CSV, the quotation marks are not displayed. If this really becomes a problem, you can easily use the find and replace feature of Notepad to find quotation mark characters and replace them with nothing.

 

RH, that is all there is to using Windows PowerShell to append one CSV file to another CSV file. As you can see, using Windows PowerShell requires no scripting to perform this operation. Join me tomorrow for more Windows PowerShell goodness.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Remove Duplicate Lines from a CSV File

$
0
0

Summary: Learn how to use Windows PowerShell to easily remove duplicates from a CSV file.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a comma-separated value (CSV) file that comes from a Microsoft Excel spreadsheet. We use this CSV file to create new users automatically in Active Directory. The problem is that every once in a while, duplicates end up in the CSV file. As you can well imagine, this creates havoc and the import fails. I began to think about how I could ensure that the data in the CSV file is unique. I was thinking of the old-fashioned dictionary object trick I used back in the VBScript days, and I tried some things using the Windows PowerShell hashtable. The problem is it quickly got rather complicated. Next, I decided to try a similar thing using an array, and once again, things went pear-shaped. I looked on the Scripting Guys Script Repository and found a pretty cool VBScript that uses a recordset and ADO. I am guessing I could modify it, but I am hoping for something easier.

—JB

 

Hey, Scripting Guy! AnswerHello JB,

Microsoft Scripting Guy Ed Wilson here. Some things are simple, such as today’s date: 11-1-11. (That’s how we present it in the United States anyway.) In fact, this year has been one of simple dates. It began with 1-1-11, and in 10 days, it will be 11-11-11. Other things appear complicated, and then a sudden revelation shows simplicity and elegance of design.

One of the hardest things for people who formerly used VBScript as they move to Windows PowerShell is to quit thinking in terms of what they would have done in VBScript. The overriding principle in Windows PowerShell is that everything is an object. “Oh yeah,” you might say, “I know that.” But until you really know that, and until you use that as fundamental in your approach, you will not actually get it. After you really know that everything is an object, you begin to look for solutions in a new and different way—one that was not possible in VBScript or some other scripting language.

A perfect case in point, JB, is your problem with needing to remove duplicates from a CSV file. First, if I am going to work with a CSV file, I need to import it. I then need to see which properties are available. To do this, I use the Import-CSV cmdlet and the Get-Member cmdlet. In the output that follows, I see four noteproperties that correspond to the column headers from the CSV file.

PS C:\> Import-Csv C:\fso\UsersConsolidated.csv | Get-Member

 

 

   TypeName: System.Management.Automation.PSCustomObject

 

Name                           MemberType                                         Definition

Equals                           Method                                                 bool Equals(System.Object obj)

GetHashCode                Method                                                 int GetHashCode()

GetType                        Method                                                 type GetType()

ToString                        Method                                                 string ToString()

Fname                           NoteProperty                                        System.String Fname=Ed

Group                           NoteProperty                                        System.String Group=Engineering

Lname                           NoteProperty                                        System.String Lname=Banti

OU                               NoteProperty                                        System.String OU=Atlanta

 

I now need to decide which duplicates I want to remove. Obviously, I do not want to remove duplicate group names or OU names. I only want to remove duplicate first name and last name combinations. It is okay to have a duplicate first name, and it is okay to have a duplicate last name—just not a duplicate first name/last name combination. I therefore need to sort the output by lname (last name) and fname(first name) columns. To do this, I use the Sort-Object cmdlet. This command is shown here (sort is actually an alias for Sort-Object):

Import-Csv C:\fso\UsersConsolidated.csv | sort lname,fname

The command to sort an imported CSV file and the associated output is shown in the following figure.

Image of command and associated output

After I have the contents of the CSV file sorted, I use the unique switch to return only unique rows from the file:

Import-Csv C:\fso\UsersConsolidated.csv | sort lname,fname –Unique

The command and associated output are shown in the following figure.

Image of command and associated output

JB, that is all there is to using the Sort-Object cmdlet and the Import-CSV cmdlet to remove duplicates from a CSV file. Join me tomorrow when we continue to explore the cool things that Windows PowerShell offers.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace

Ed Wilson, Microsoft Scripting Guy

 

 

Remove Unwanted Quotation Marks from CSV Files by Using PowerShell

$
0
0

Summary: Learn how to remove unwanted quotation marks from a CSV file by using Windows PowerShell.

 

Microsoft Scripting Guy Ed Wilson here. The fall is rapidly falling down here in Charlotte, North Carolina, in the United States. It is cooling off here, and is around 60 degrees Fahrenheit (15.5 degrees Celsius, according to my conversion module). The neighbors have all been wracked with anguish over whether to tough it out for a few more days, or to go ahead and turn on the heater. It is, evidently, such a convoluted decision that they have made numerous Facebook postings about it. But I guess I would rather read about anguished decisions about to turn on the heater or not, than to read about the latest nonferrous material that their pet consumed. 

The Internet is cool. For example, I really enjoy comments that are posted on the Hey, Scripting Guy! Blog. In fact, I have subscribed to a RSS feed that alerts me any time that a new comment is made, regardless of how old the article may be. Just this morning, I replied to two comments from readers on postings that were more than five years old; that’s 35 in doggy years, and more like 50 in Internet years.

Anyway, the other day, I wrote a pretty cool article named, Use PowerShell to Append CSV files Easily.In the comments about that article, Jeffrey S. Patton from Kansas (yes Toto, he is from Kansas) posted a asked why I used Notepad to remove commas instead of programmatically replacing the commas in the output with Windows PowerShell. I answered that I did it because opening the file in Notepad, clicking Edit, and then clicking Replace was faster and easier than writing the extra code to replace the quotation marks. In a one-off scenario, that is probably true. But what if this is something that needs to be scheduled on a regular basis? If it is an automation scenario, opening the file in Notepad is off the table; as cool as Notepad is, it does not have an automation interface, so it is not a scriptable solution.

What exactly is the problem? When using the Export-CSV cmdlet, it automatically adds quotation marks around everything. In some cases, when reading this file, the quotation marks could be interpreted as part of the data, which of course would be a problem. It is rare, but it most certainly could happen. In fact, this very problem is one reason I quit using Microsoft Excel to manipulate CSV files: it has the same “feature.” This happens because if you have a space in a column, some programs are not smart enough to respect the space between commas. Therefore, to help prevent an error with a column that has a space in it, quotation marks are added everywhere. The output file produced from our command from yesterday (shown here) is shown in the figure following this code:

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Import-Csv | sort lname,fname | Export-Csv -Path c:\fso\UsersConsolidated.csv –NoTypeInformation

Image of output file with quotation marks

Jeffrey was absolutely correct when he said I could use replace to remove the quotation marks from the output file. It is actually pretty simple.

Here are the steps I need to perform:

  1. Use the Get-Content cmdlet to read the text of the usersconsolidated.csv file.
  2. Use the Foreach-Object cmdlet (% is an alias) to read each line as it comes from the file.
  3. Inside the script block for the Foreach-Object command, use the $_ automatic variable to reference the current line and the replace operator to replace a quotation mark with nothing.
  4. Use the Out-File cmdlet to overwrite the existing usersconsolidated.csv file with the newly changed content that no longer contains the quotation marks.
  5. Use the force switched parameter to tell the Out-File cmdlet to overwrite existing content.
  6. Use the encoding parameter to specify ASCII encoding to maintain compatibility with legacy applications.

The command line I use is much simpler than the six steps above would make it seem. Here is the command:

(Get-Content C:\fso\UsersConsolidated.csv) | % {$_ -replace '"', ""} | out-file -FilePath C:\fso\UsersConsolidated.csv -Force -Encoding ascii

The preceding command is a single-line command that has wrapped. No line continuation marks are used in the command. The parentheses around the Get-Content cmdlet are required (otherwise, it creates a blank file). I can actually shorten this command by searching for additional aliases. I use the Get-Alias cmdlet to search for aliases. The cool thing is that it will accept an array for input, and therefore I can look for aliases for both Get-Content and Out-File at the same time. Here is the command:

Get-Alias -Definition get-content, out-file

The bad thing about the Get-Alias cmdlet is that it returns an error when no match appears. This behavior is shown in the following figure.

Image showing error when no match appears

The shortened version of the command uses the gc alias for the Get-Content cmdlet. It also uses positional parameters and partial parameter names.

(gc C:\fso\UsersConsolidated.csv) | % {$_ -replace '"', ""} | out-file C:\fso\UsersConsolidated.csv -Fo -En ascii

As shown in the following figure, there are no more quotation marks in the file.

Image of output file with no quotation marks

If you want to do this all in a single command, it is easier to switch to using the ConvertTo-CSV cmdlet instead of the Export-CSV cmdlet. The reason is that ConvertTo-CSV converts to CSV format, but does not export to a file. This allows time to replace the quotation marks prior to writing to file. The revised command is shown here:

dir c:\fso -Filter *.csv | ? {$_.basename -like 'users?'} | Import-Csv |  sort lname,fname | convertto-csv -NoTypeInformation | % { $_ -replace '"', ""} | out-file c:\fso\usersconsolidated.csv -fo -en ascii

There is no alias for the ConvertTo-CSV cmdlet, but there is an alias for Import-CSV, which is ipcsv. I can also use ls as an alias for Get-ChildItem, instead of the lengthier dir alias. I can also shorten filter to fi (I cannot use the single letter f because there is also a parameter force defined for the Get-ChildItem cmdlet). Here is the shortened command:

ls c:\fso -Fi *.csv | ? {$_.basename -like 'users?'} | ipcsv | sort lname,fname | convertto-csv -NoTypeInformation | % { $_ -replace '"', ""} | out-file c:\fso\usersconsolidated.csv -fo -en ascii

Well, that is about all there is to removing quotation marks from a CSV file. Join me tomorrow for more Windows PowerShell fun.

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Search for and Replace Words in a CSV File by Using PowerShell

$
0
0

Summary: Learn how to search for and replace words in a CSV file by using Windows PowerShell.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a comma-separated value (CSV) file that contains user names, user groups, and organizational unit (OU) assignments. The problem is that we are moving one of our office locations, and I need to quickly change the old location name to the new location name. I have been reading all of your articles this week, and I think I should be able to use Import-CSV to read the CSV file, and use a foreach loop to evaluate each row in the CSV file.

Inside there, I should be able to use an if statement to see if there is a match with the old name. If there is, I want to change it to the new name, and then export the CSV data to a new file. The problem is that this is not really working the way I want it to. For some reason, I appear to be picking up some kind of extra crap that is added by Windows PowerShell. I have spent all afternoon on something that should really be a simple one-liner. Help please. If I have to write a script to do this, I may as well go back to using VBScript. At least I have plenty of years experience using that language.

—GM

 

Hey, Scripting Guy! AnswerHello GM,

Microsoft Scripting Guy Ed Wilson here. One thing that is rather interesting about Windows PowerShell is that some things are really super easy. Other things appear to be difficult until you find the “correct” approach. One of the things I noticed in the 2011 Scripting Games is that some people work way too hard on their solutions. The other cool thing about Windows PowerShell is that in the end, if something runs without errors and something does what you need to be done, it is a solution. If I can write a bit of code in five minutes that is ugly but works, it is probably better than something that is elegant but takes me five hours to write. Don’t get too hung up on trying to craft the ultimate one-liner when you have a job to do, and that job does not entail crafting the ultimate one-liner.

On the other hand, there is something to be said for thinking about how to do things more effectively in Windows PowerShell. GM, I am imagining that your approach was to do something like this:

import-csv C:\fso\usersconsolidated.csv | foreach { If($_.ou -match "Atlanta") {$_.OU -replace "Atlanta","Cobb"}} | export-csv c:\myfolder\myfile.csv

This command does not work the way you want it to work … in fact, it does not really work very well at all because it does not produce the expected results. You are getting hung up with wrestling with the pipeline, getting confused with the Foreach-Object cmdlet (that is interrupting your pipeline), and messing about with the Export-CSV command that is not producing a CSV file at all.

While it is true that Windows PowerShell has some pretty cool cmdlets for working with CSV and for working with files, it is also true that at times, I want to be able to read the entire contents of a file into memory, and work on it at one time. The Get-Content cmdlet does not permit this; it basically creates an array of lines of text, which is great on most occasions.

To easily read the contents of a file all at once, I use the readalltext static method from the File class from the .NET Framework. The File class resides in the System.IO .NET Framework namespace. When using this class, I use square brackets around the class and namespace name. I can leave off the word system if I want to, or I can type it if I wish—it does not matter. If the word system is not present, Windows PowerShell will assume that the namespace contains the word system in it and will automatically use that when attempting to find the class. The .NET Framework namespaces are similar to the namespaces used in WMI because they are used to group related classes together for ease of reference. The difference is that the .NET Framework namespaces are a bit more granular. Therefore, if I am interested in working with files, directories, paths, file information, and other related items, I would go to the System.IO .NET Framework namespace and look around to see what is available.

After I find the File class, I can look it up on MSDN to see which methods it includes. The easiest ones to use are the static members (methods, properties, and events taken together become members) because using Windows PowerShell, all I need to do is to put the namespace/class name combination inside square brackets, use two colons, and the name of the method. And it works. Many times, the things a class provides are available somewhere else. For example, the File .NET Framework class provides a static method called exists. This method returns a Boolean value (true or false) that lets me know if a file exists or not. To use this method, I provide a string to the method. This technique is shown here:

PS C:\> [io.file]::exists("C:\fso\UserGroupNames.txt")

True

PS C:\> [io.file]::exists("C:\fso\missingfile.xxx")

False

I can accomplish the same thing by using the Test-Path cmdlet. This appears here.

PS C:\> Test-Path C:\fso\UserGroupNames.txt

True

PS C:\> Test-Path C:\fso\missingfile.xxx

False

It is always preferable to use a native Windows PowerShell cmdlet to do something, rather than resorting to .NET Framework, COM, WMI, ADSI, or some other technology—unless you have a compelling reason for doing otherwise.

A static method called readalltext is available from the file class, and it can be used in two ways. The first way is to supply a string that points to the path to the file to open. The second way is to specify the encoding of the file. Most of the time when reading a file, the encoding is not required because the method attempts to detect automatically the encoding of a file based on the presence of byte order marks. Encoding formats UTF-8 and UTF-32 (both big endian and little endian) can be detected. The result of using the readalltext method is that I get back a bunch of text in the form of a String class. The String class resides in the System .NET Framework namespace, and it contains a large number of methods. One of those methods is the replace method. I therefore add the replace method to the end of the readalltext method. The command to read all of the text in a file (CSV file) and to replace every instance of the word atlanta with the word cobb is shown here:

[io.file]::readalltext("C:\fso\usersconsolidated.csv").replace("atlanta","cobb")

The command to read a text file and replace words, and its associated output are shown in the following figure.

Image of command and associated output

To write this to a text file is simple. I can use the Out-File cmdlet as shown here:

[io.file]::readalltext("C:\fso\usersconsolidated.csv").replace("Atlanta","Cobb") | Out-File c:\fso\replacedAtlanta.csv -Encoding ascii –Force

The above is a single-line command that wraps in my Word document. I have not added any line continuation to the command. Keep in mind this technique is case sensitive. It will replace Atlanta, but not atlanta. The newly created text file is shown in the following figure.

Image of newly created text file 

GM, that is all there is to replacing values in a CSV file. Join me tomorrow for more exciting Windows PowerShell fun. TTFN.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 


Use a CSV File to Populate Parameters of PowerShell Cmdlets

$
0
0

Summary: Learn how to use a CSV file to populate parameters of Windows PowerShell cmdlets.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a problem. I would like to be able to read content from a comma-separated value (CSV) file that contains a list of WMI classes and servers that I would like to query. I need to be able to read the CSV file, and based upon the data contained in that CSV file, I need to create a query on the fly and execute that query. I am afraid it is too complicated, but if I can do this, it will make things a whole lot easier for us at work. Do you know any secret tricks to help us out of our dilemma?

—SC

 

Hey, Scripting Guy! AnswerHello SC,

Microsoft Scripting Guy Ed Wilson here. Getting to go to different places and talk to people about Windows PowerShell is a lot of fun. However, there is a downside—expense reports. I wish there were a Windows PowerShell cmdlet called Get-Expense, and another one called New-ExpenseReport. If there were, I could use a command such as this one:

Get-Expense | New-ExpenseReport

That is the beauty of the Windows PowerShell pipeline. It looks at incoming properties, and seems to match them with similar items on the other side. Therefore, I can easily type the following:

Get-Process | Stop-Process

And the command works. This is a really cool example because the Get-Process default property is name and the default property for Stop-Process is id. The Windows PowerShell pipeline figures this stuff out. Cool.

If a cmdlet or advanced function does not accept piped input, all is not lost because you can always use the Foreach-Object cmdlet and manually map things inside that cmdlet’s scriptblock.

The key here is that when importing a CSV file via the Import-CSV cmdlet, each column becomes a property of an object. The other key here is that in a pipeline situation, $_ refers to the current item on the pipeline.

Using this as background, I want to look at a sample CSV file such as you described. The first column contains a WMI class name, and the second column is the computer name to use. The sample CSV file is shown in the following figure.

Image of sample CSV file

The following steps are required to read a CSV file and create a WMI query.

  1. Use the Import-CSV cmdlet to import the CSV file.
  2. Use the ForEach-Object cmdlet to walk through the imported data as it comes across the pipeline.
  3. Inside the script block of the Foreach-Object cmdlet, use the Get-WMIObject cmdlet to query WMI.
  4. Map the CSV column headings to the parameters of the Get-WmiObject cmdlet.
  5. Use the $_ variable to refer to the current items on the pipeline.

The command to read a CSV file named WMICSVFile.csv that resides in the C:\fso directory and to query WMI is shown here (in the following command, % is an alias for the Foreach-Object cmdlet, and gwmi is an alias for the Get-WmiObject cmdlet):

import-csv C:\fso\WMICSVFile.csv | % {gwmi $_.class -cn $_.cn}

The command to read a CSV file and automatically query WMI along with the associated output is shown in the following figure.

Image of command and associated output

 

SC, that is all there is to using a CSV file for input to other commands. This also concludes CSV Week. Join me tomorrow for the Weekend Scripter.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Read CSV Files and Create Files and Folders

$
0
0

Summary: Learn how to use Windows PowerShell to read CSV files and create files and folders.

 

Microsoft Scripting Guy Ed Wilson here. This past week has been fun. I enjoy talking about comma-separated value files (CSV) because they are very flexible, and Windows PowerShell makes working with them really easy.

One of the cool things that can be done using a CSV file was only alluded to in yesterday’s post, Use a CSV File to Populate Parameters of PowerShell Cmdlets.

In yesterday’s article, I piped information from a CSV file to the Get-WmiObject cmdlet. The problem is that the Get-WmiObject cmdlet does not accept piped input for either the class or computername parameter.

However, the New-Item cmdlet does accept piped input. When a cmdlet accepts piped input, it will automatically match column names from a CSV file with parameter names of the cmdlet receiving the piped input. This allows for some extremely powerful scenarios. Here are the steps for this scenario:

  1. Create a CSV file that uses column headings that are the same as the parameter names for the cmdlet to use.
  2. Use the Import-CSV cmdlet to read the CSV file and to create a custom object from that file.
  3. Pipe the results to a cmdlet that accepts piped input.

Suppose I create a CSV file that contains a file system layout. In that file, I specify the name of the folder and the names of files I want to create in that folder. I use the column names of path and itemtype because those are the parameter names used by the New-Item cmdlet. A sample CSV file that meets these requirements is shown in the following figure

Image of CSV file that meets requirements

After I have created my file, the Windows PowerShell command itself is trivial. I use the Import-CSV cmdlet to read the CSV file, and I pipe the results to the New-Item cmdlet. This command is shown here:

Import-Csv C:\fso\FilesAndFolders.csv | New-Item

The use of the command to read a CSV file and create files and folders along with the associated output are shown in the following figure.

Image of command and associated output

When this command runs, the following is displayed in Windows Explorer.

Image of what is displayed in Windows Explorer when command runs

That is about all there is to reading a CSV file and automatically creating files and folders. Given the Windows PowerShell providers that work with New-Item, this one scenario and one cmdlet provide lots of possibilities. And this is only one cmdlet. I will leave it to you to explore and see what you can come up with. Let me know.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Automatically Create a CSV File

$
0
0

Summary: Learn how to use Windows PowerShell to automatically create a CSV file.

 

Microsoft Scripting Guy Ed Wilson here. I am about finished playing around with CSV files for the time being. I do not always use the ConvertTo-CSV or the Export-CSV cmdlet. There are times that I write code and create my own CSV file from scratch. An example of this is the CreateCSVFile.ps1 script that follows:

CreateCSVFile.ps1

1..5 | ForEach-Object -begin { "path,itemtype" } -process{

 "c:\folder$_,directory"

 for($i=0; $i -le 10; $i++) {"c:\folder$_\file$i.txt,file"}} |

 Out-File c:\fso\filesAndFolders.csv -Encoding ascii -Force

This script creates a CSV file that I can use to create a nested folder hierarchy. It is useful when testing certain applications, or for testing a Windows PowerShell script. I create five folders, and inside each folder I create 11 files. The folders are off the root and are named Folder1, Folder2, Folder3, Folder4, and Folder5. The files are named file0, file1, and so on through file10.

When using the Foreach-Object, I use the begin parameter to perform an action once. Inside the process block, I create the path to the folder, and I include the itemtype of directory. Next, I use a loop to create the path to the files.

The newly created CSV file is shown in the following figure.

Image of newly created CSV file

I use the Import-CSV cmdlet to import the CSV file, and I pipe it to the New-Item cmdlet as shown here:

Import-Csv C:\fso\FilesAndFolders.csv | New-Item

The command and associated output are shown in the following figure.

Image of command and associated output

One of the folders and associated files is shown in the following figure.

Image of folder and files

As you can see, there are times when it is easier to use a little bit of code to create the CSV file. This makes it a lot easier when it comes time to import the file and to pipeline it to another cmdlet.

 

This is all for today. I hope you have a great day. See you next week.

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Two Powerful Tricks for Finding PowerShell Scripts

$
0
0

Summary: Microsoft Scripting Guy Ed Wilson shares two powerful tricks for finding Windows PowerShell scripts.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am wondering if you have any cool ways to help me find the scripts I have written. I have quite a few scripts in my scripts folder, but opening Windows Explorer and searching through them is slow, cumbersome, and time consuming. I am wondering if a guy like you has any cool tricks you would be willing to share to help me be more efficient.

—EB

 

Hey, Scripting Guy! AnswerHello EB,

Microsoft Scripting Guy Ed Wilson here. Last week, the Scripting Wife and I had a great luncheon meeting with Microsoft Windows PowerShell MVP Jim Christopher while we were in Charlotte, North Carolina. The subject of the meeting was starting a Windows PowerShell user group in Charlotte, North Carolina. Yes, that is right; we are going to start a new Windows PowerShell user group in Charlotte. It was a really cool meeting that ended up going on for several hours. Jim is a really great person, and he has a lot of great ideas. Right now, it looks like the first meeting will be in January, but stay tuned.

If you live in the southeastern section of the United States, you may want to be making travel plans to attend the inaugural meeting. It will be cool! I have reserved a room at the Microsoft office in Charlotte, but it only holds 200 people. So this user group meeting will definitely be on a first come, first served basis. I am certain we will also have Live Meeting set up for the event. I am really psyched. The Scripting Wife is helping Jim get everything set up, so stay tuned for Scripting Wife blog posts about the experience. This is really going to be great; I could go on and on with superlatives, but EB, I need to answer your question.

EB, I have a very cool solution to help you find and work with your Windows PowerShell scripts in a more efficient manner. It is a two-part solution. Here are the two steps:

  1. Add your script directory to the path by using $env:path. Make this addition part of your Windows PowerShell profile.
  2. Use the Get-Command cmdlet to find your scripts.

The first thing to do is to add your script directory to the path. The nice thing about using the environment PSDrive is that changes happen immediately. There is not a need to close and open Windows PowerShell. The other nice thing about using the environment PSDrive is that changes are not permanent. It is therefore possible to add the scripts directory to the path, but not make any permanent changes to the path. Each folder in the path uses a semicolon delimiter. The final folder does not have a semicolon after it. In the following figure, I first query the existing path. Next, I add the C:\fso folder to the path. The final command I use checks the path to see if the new folder appended correctly. The three commands are shown here:

$env:path

$env:path +=';c:\fso'

$env:path

The commands and associated output are shown in the following figure.

Image of commands and associated output

After I have added the scripts folder (C:\fso in this example), I can use the Get-Command cmdlet to find all the scripts in the folder. To do this, I use the Get-Command cmdlet with the commandtype of externalscript. This command is shown here:

Get-Command -CommandType externalscript

Any Windows PowerShell script from any folder that appears in the path is shown in the output. The command and associated output are shown in the following figure.

Image of command and associated output

Interestingly enough, the definition property contains the path to the script itself. The default output does not display script contents. If I want to limit the number of scripts returned by the command, I can use the totalcount parameter. The following command returns only one Windows PowerShell script:

Get-Command -CommandType externalscript -TotalCount 1

To get an idea of the information that the Get-Command cmdlet is able to return, send the output to the Format-List cmdlet and choose all properties:

Get-Command -CommandType externalscript -TotalCount 1 | format-list -Property *

The command and associated output are shown in the following figure.

Image of command and associated output

The ScriptBlock and the ScriptContents properties contain useful information. I can therefore use the Get-Content cmdlet to search my scripts that contain a function named addone. This command and output are shown here:

PS C:\> Get-Command -CommandType externalscript | Where-Object { $_.scriptblock -match 'addone'}

 

CommandType     Name                            Definition

ExternalScript  a.ps1                           c:\fso\a.ps1

 

By using aliases, I can shorten the command syntax a good deal. Here is the short version of the command (I use the alias gcm for Get-Command, and the alias ? for the Where-Object cmdlet; I also use the –c as a short form of commandtype):

gcm -c externalscript | ? { $_.scriptblock -match 'addone'}

Unfortunately, I cannot use neither an alias for the externalscript enumeration nor a wildcard character. In the following figure, I attempt to use a wildcard character to avoid typing externalscript. The command and associated error are shown.

Image of command and associated error

Because I now have the enumeration name, I can use my Get-EnumValues function from my Enumerations and Values blog post. By using the Get-Enumvalues function, I discover that externalscript has a value of 16. This is shown in the following figure.

Image showing externalscript has value of 16

Cool! Now that I know I can use an enumeration value, I can really shorten the command:

gcm -c 16

This command is short enough such that it is really easy to type. For example, if I am looking for my function that creates a new temporary file, but I am not certain of the exact name, I can use the following to find it:

PS C:\> gcm -c 16 | ? { $_.scriptblock -match 'tmp'}

 

CommandType     Name                            Definition

ExternalScript  Out-TempFile.ps1                c:\fso\Out-TempFile.ps1

EB, that is all there is to adding a folder to the path and using the Get-Command cmdlet to find scripts. Because the match operator accepts a regular expression, I have a really powerful way to search my Windows PowerShell scripts to find exactly what I am looking for. Join me tomorrow for more Windows PowerShell goodness—you will be glad you did!

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Pause a Script While Another Process Exits

$
0
0

Summary: Learn how to use Windows PowerShell to pause a script and wait for another process to exit before continuing.

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am attempting to use Windows PowerShell to shut down an application, but the application is rather complicated. I need to stop one process, and wait for that process to complete before starting the second process. I have attempted to use the Start-Sleep cmdlet to pause script execution to allow for the different processes to complete, but unfortunately different systems are faster than others so this is a hit-or-miss proposition. Can you figure out a better way of doing this?

—SK

 

Hey, Scripting Guy! AnswerHello SK,

Microsoft Scripting Guy Ed Wilson here. Well, I am floating along at 35,000 feet (10,850 meters) listening to Stan Kenton’s Waltz of the Prophets, which in my mind (as a former saxophone major at the university) is one of the absolutely best jazz standards ever written. I have been playing around with Windows PowerShell for the last several hours (decided to group a bunch of my utility functions into a Windows PowerShell module, create a manifest, and install them into my Windows PowerShell folder by using my Copy-Modules function). You should really be investing in writing Windows PowerShell modules instead of writing one-off scripts, if possible. The effort will pay great dividends.

Anyway, I am sitting in First Class (I was upgraded) with a decent worktable, and my new laptop will get over eight hours and still provide decent performance, so life is good. The solitude provides a wonderful time for exploring and for playing with Windows PowerShell. I am forcing myself to stay away from WMI for now, and am instead focusing on different ways of putting together cmdlets, to see if I can come up with something cool. I was pretty pleased with the path trick I came up with in yesterday’s Two Powerful Tricks for Finding PowerShell Scripts post.

One of the things I was playing around with is the Wait-Process cmdlet. It is most useful when used in a script in that it will halt execution of a script until a process terminates. This allows an easy way to write a script that terminates multiple processes—one at a time.

There are two ways that the Wait-Process cmdlet accepts input: either a process name or a process ID. For example, I can start an instance of Notepad and then use Wait-Process to pause until Notepad closes.

Notepad

Wait-Process notepad

When any instance of the Notepad process exits, control to the script (or Windows PowerShell console) returns. This works when you do not care which copy of Notepad closes, or when you know there is only a single instance of a process running.

A more useful way to do this is to capture the process ID of the process when it launches, and use that specific process ID with the Wait-Process cmdlet. This technique uses the Invoke-WmiMethod cmdlet and is shown here:

$proc = Invoke-WmiMethod -Class win32_process -Name create -ArgumentList "notepad"

Wait-Process -Id $proc.ProcessId

One of my favorite tricks (but it is not in my top ten favorite Windows PowerShell tricks) pipes Notepad to Out-Null to halt processing the script until Notepad closes. I then remove the copy of the text file that was viewed. This is a great way to handle temporary files. This technique is shown in the following code:

$tmpfile = [io.path]::GetTempFileName()

get-process >> $tmpFile

notepad $tmpfile | out-null

Remove-Item $tmpfile

test-path $tmpfile

The problem with this “trick” is that it does not work for other things. For example, if I write process information in HTML format and display that in Internet Explorer, the Out-Null trick does not halt the script. In addition, if I attempt to close Internet Explorer without a specific process ID, I might close more than once instance of Internet Explorer. The code that follows creates a temporary file in a temporary location. It changes the file extension to HTML, and then uses the Convertto-HTML cmdlet to output HTML formatted data from the Get-Process cmdlet. I use the Out-File cmdlet to write my HTML formatted data to a file, and then I use the Invoke-Item cmdlet to open the HTML file in Internet Explorer (at least on my system; this methodology will open the HTML file with the default program associated with that extension):

$tmpfile = [io.path]::GetTempFileName()

$tmpFile = "{0}.{1}" -f ($tmpfile).split(".")[0],"html"

Get-process | ConvertTo-HTML |

Out-File -FilePath $tmpFile -Encoding ascii -append

invoke-item $tmpfile

Note   See the excellent guest Hey, Scripting Guy! Blog post, Proxy Functions: Spice Up Your PowerShell Core Cmdlets, written by Microsoft PowerShell MVP Shay Levy for a way to add a filepath parameter to the ConvertTo-HTML cmdlet. By creating a proxy function, you can avoid a call to Out-File.

The Out-Null trick will not pause the script and allow for cleanup, and we would not know which version or instance of Internet Explorer might be killed anyway, so there needs to be a new way. Here are the steps involved:

  1. Create a temporary file name.
  2. Change the file extension to html.
  3. Pipe data to the ConvertTo-Html cmdlet.
  4. Pipe the data from ConvertTo-Html to Out-File, and create an HTML document.
  5. Use Invoke-WMIMethod to open the file and store the return values.
  6. Use the Wait-Process cmdlet to pause execution of the script and wait for the process to close.
  7. Use Remove-Item to remove the temporary file.

The GetProcessInfoDisplayHTMLtrackProcessAndRemoveTmpFile.ps1 script illustrates these steps.

GetProcessInfoDisplayHTMLtrackProcessAndRemoveTmpFile.ps1

$tmpfile = [io.path]::GetTempFileName()

$tmpFile = "{0}.{1}" -f ($tmpfile).split(".")[0],"html"

$iepath = Join-Path -Path ${env:ProgramFiles(x86)} `

        -ChildPath "internet explorer\iexplore.exe"

Get-process | ConvertTo-HTML |

Out-File -FilePath $tmpFile -Encoding ascii -append

$iepid = Invoke-WmiMethod -Class win32_process -Name create -Argument "$iepath $tmpfile"

Wait-Process -Id $iepid.ProcessId

Remove-Item $tmpfile

In the script, the first thing I do is get a temporary file name. The GetTempFileName method creates a temporary file name in the temporary location. An example of such a file name is shown here:

C:\Users\edwilson\AppData\Local\Temp\tmp1573.tmp

I split the string at the period. This is not a reliable method for splitting file names and removing the file extension, but it does work here. For example, this will fail if a file name has two periods in the name. I have, on occasion concatenated the temporary file name with “.html” and ended up with a filename with two periods in it, which is, of course, a perfectly acceptable file name. Here I am simply exploring other ways of creating a temporary file name with an HTML file extension. Internet Explorer requires a file to have an HTML file extension, or it will not render the page properly. For example, if I stick with the .tmp file extension, the page renders as shown in the following figure.

Image of how page renders with .tmp file extension

I like to use the Join-Path cmdlet to build paths. To do this, I need to supply the path (parent path) portion and the childpath (child path) portion of the path. I am not restricted to simply path\executable in my formatting. I can, for example, include additional directories in my childpath portion of the command. In this example, I use an environmental variable to retrieve the path to the x86 program files, and then I add the remainder of the path to the Internet Explorer executable. I broke the command, and used the backtick (`) character for line continuation. I would not normally do this, but it is needed to fit it on the page. This portion of the script follows:

$iepath = Join-Path -Path ${env:ProgramFiles(x86)} `

        -ChildPath "internet explorer\iexplore.exe"

Note   When copying code from the Hey, Scripting Guy! Blog, it is possible that extraneous characters appear after the backtick character, and that invisible character will actually break the code. The fix is to paste the code in the Windows PowerShell ISE and use the backspace character to erase the invisible characters that follow the backtick character.

I now create a HTML page by using the Convertto-HTML cmdlet. I write the HTML code to a file by using the Out-File cmdlet. This code is shown here:

Get-process | ConvertTo-HTML |

Out-File -FilePath $tmpFile -Encoding ascii -append

I use the Invoke-WMIMethod cmdlet to call the create method from the Win32_Process WMI class. I use this methodology because it returns the process ID of the newly created instance of Internet Explorer. I store the process ID in a variable called $iepid. This code is shown here:

$iepid = Invoke-WmiMethod -Class win32_process -Name create -Argument "$iepath $tmpfile"

I now halt execution of the script until the newly created instance of Internet Explorer goes away. After it goes away, I call the Remove-Item cmdlet, and I delete the temporary HTML file, as shown here:

Wait-Process -Id $iepid.ProcessId

Remove-Item $tmpfile

The HTML file that the script creates and displays is shown in the following figure.

Image of HTML file the script creates and displays

 

SK that is all there is to using the Wait-Process cmdlet to pause script execution and wait for a process to end. I invite you to join me tomorrow for more cool Windows PowerShell stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Troubleshoot Outlook Problems with PowerShell

$
0
0

Summary: Microsoft Scripting Guy Ed Wilson teaches how to use Windows PowerShell to troubleshoot Microsoft Outlook problems.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a problem with Microsoft Outlook on my laptop. It seems that it goes to sleep or something. I will be working, go to check my email, and nothing appears. But if I look at my Windows 7 mobile phone, it shows that new email is in my inbox. I am thinking that I have a problem with Outlook.

—BT

 

Hey, Scripting Guy! AnswerHello BT,

Microsoft Scripting Guy Ed Wilson here. The Scripting Wife and I had a wonderful time at the Geek Ready conference. She knows many of the Microsoft premier field engineers (PFEs) because she has met them at various Windows PowerShell User group meetings, at conferences, or just from hanging around me. She had a chance to meet several PFEs who have written guest Hey, Scripting Guy! Blog articles, but that she had never met in person. It really was awesome. We were up late on several nights having “Windows PowerShell side meetings” with various PFEs, and on a couple occasions, we ended up having impromptu script club meetings. The results of all this engagement will be appearing for months to come on the Hey, Scripting Guy! Blog, so stay tuned.

Anyway, with all the geeks taking over the hotel where we were staying, poor Microsoft Outlook was struggling. In fact, Internet connectivity was spotty most of the time, just due to the sheer magnitude of the demands placed on the infrastructure by us. The nice thing is that, by using Windows PowerShell, I can do an awful lot of discovery.

When I am doing event log exploration, things go a whole lot faster if I store the particular lot in a variable. So that is what I am going to. Microsoft Outlook writes events to the application log using the source id of outlook. I therefore use the Get-EventLog cmdlet and gather up all the entries that have a source of outlook. I store these EventLogEntry objects in a variable I call $log. The EventLogEntry .NET Framework class appears in the System.Diagnostics namespace, and documentation appears on MSDN. This command is shown here:

$log = Get-EventLog application -Source outlook

Once I have all of the outlook entries in a variable, I can begin to examine them. I am curious about how many entries I have, so I use the count property to see. This command and output are shown here:

PS C:\> $log = Get-EventLog application -Source outlook

PS C:\> $log.count

280

Next, I want to see what type of structure I am dealing with, so I index into the array of records, and pick off one record to examine and pipe the results to the Format-List cmdlet (fl is an alias for the Format-List cmdlet). Here is the command I use:

$log[0] | fl * -Force

This command and the associated output appear in the following figure.

Image of command and associated output

Wow, as it turns out, that was a bad example because it goes on and on and on. It can be useful, however, because this shows me all of the add-ins that Microsoft loads. Remember, seeing the Microsoft Outlook splash screen that shows how many add-ins it is loading? It looks like EventID 45 tells me about loading add-ins.

A better event log entry is the one that is shown in the following figure. The reason this event log entry is better is that it allows me to see representative data from all of the different properties in a single screen shot.

Image of better event log entry

Event ID 26 looks like it tells me when Microsoft Outlook has lost connectivity to the Microsoft Exchange server. Hmmm, that might be useful. Let me look at all event ID 26s and see what they say. To do this, I pipe the collection of event log entries that are stored in the $log variable to the Where-Object cmdlet (? Is an alias for Where-Object). In the script block associated with the Where-Object cmdlet, I look for eventid that is equal (-eq) to 26. This command is shown here:

$log | ? {$_.eventid -eq 26}

My screen quickly floods with entries. Interestingly enough, it seems that Event ID 26 reports lost connectivity as well as restored connectivity. This can actually be a useful thing. What I can do first is look at how many disconnects and how many connection restored messages there are. This command is shown here:

PS C:\> $log | ? {$_.eventid -eq 26} | group message -NoElement | select name, count | ft -AutoSize

Name                                                                                               Count

----                                                                                               -----

Connection to Microsoft Exchange has been lost. Outlook will restore the connection when possible.    36

Connection to Microsoft Exchange has been restored.                                                   34

Now that I see that there are a significant number of times when the connection to the Microsoft Exchange server dropped, I would really like to see when this is happening. To do this, I want to focus on the timewritten property from the eventlog. The problem is that if I find my events, and group by the timewritten property, the result will be 70 separate lines and no real grouping because each timewritten record will be unique. Therefore, nothing exists to group on. The command is shown here:

$log | ? {$_.eventid -eq 26} | group timewritten

The command and output are shown in the following figure.

Image of command and output

The trick is to realize that the timewritten property contains a DateTime object. This is important because I know that an instance of the DateTime object exposes a day property. I can then use the Group-Object cmdlet to organize the eventlog records by day. I used the Get-Member cmdlet (gm is an alias) to discover that the timegenerated property contains a DateTime object. This command and output are shown here:

PS C:\> $log[0] | gm timegenerated

   TypeName: System.Diagnostics.EventLogEntry#application/Outlook/1073741869

 

Name          MemberType Definition

----          ---------- ----------

TimeGenerated Property   System.DateTime TimeGenerated {get;}

 

The problem is exposing that DateTime object to the Group-Object cmdlet. For example, the following command attempts to use dotted notation to directly access the day property of the DateTime object:

$log | ? {$_.eventid -eq 26} | group timewritten.day

The command and output are shown here (they are not impressive):

PS C:\> $log | ? {$_.eventid -eq 26} | group timewritten.day

Count Name                      Group

----- ----                      -----

   70                           {System.Diagnostics.EventLogEntry, System.Diagnostics.EventLogEntry,

The following commands also do not work. In fact, some generate errors:

$log | ? {$_.eventid -eq 26} | group (timewritten).day

$log | ? {$_.eventid -eq 26} | group $(timewritten).day

$log | ? {$_.eventid -eq 26} | group $_.timewritten.day

The trick is to use the Select-Object cmdlet with the expandproperty parameter to expand the timewritten property from the Get-EventLog. In this way, I can then use the Group-Object cmdlet to group the eventlog records by day. I decided to leave the details because they let me see which days I am having problems. The command is shown here:

$log | ? {$_.eventid -eq 26} | select -expandproperty timewritten | group day

The command and associated output are shown in the following figure.

Image of command and associated output

It is obvious from the preceding figure, that there were problems on November 4 and October 16. This is great information because I could work with a customer, or someone who says, “I had a problem with Outlook last week sometime. I don’t really remember when, but it seemed like it kept dropping off, and not working.” And with Windows PowerShell and the Get-EventLog cmdlet, I can actually connect remotely to their computer and retrieve the information I need. And then I can say, “Yes, I see you had a problem on November 4, but we were applying patches to the Exchange server that day, and it was up and down all day. So, no, there is no problem with Outlook.”

But what if we look back, and we were not performing maintenance? Maybe, instead I want to see if there is a pattern by hour. I also know that the DateTime object contains an hour property. Therefore, using my trick from earlier, I come up with the following command:

$log | ? {$_.eventid -eq 26} | select -expandproperty timewritten | group hour

The command and associated output follow. It is very revealing. Thirty-six of the disconnects occurred between the hours of 20:00 (8:00 P.M.) and 22:00 (10:00 P.M.).

Image of command and associated output showing disconnects

 

BT, that is all there is to using Windows PowerShell to assist in troubleshooting Microsoft Outlook problems. Join me tomorrow for more cool Windows PowerShell things.  

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Find User Profiles on a Computer

$
0
0

Summary: Learn how to use Windows PowerShell to find all user profiles on a computer, and to display the date when each profile was last used.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I would like to find a good way to see which profiles exist on my laptop. I found a Hey, Scripting Guy! post to do this, but it uses VBScript. Can this be done using Windows PowerShell?

—CW

 

Hey, Scripting Guy! AnswerHello CW,

Microsoft Scripting Guy Ed Wilson here. A few years ago (actually more like six years ago), there was a Hey, Scripting Guy! Blog post entitled Hey, Scripting Guy! How Can I List All the User Profiles on a Computer? That post talks about enumerating a registry key to find the profile information. The registry location did not change in Windows 7, so the VBScript would still work. Here is the registry location:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList

The registry location viewed in the Registry Editor appears in the following figure.

Image of registry location

Using Windows PowerShell, it is really easy to get and to display registry keys. I can enumerate the profile keys in a single command. However, due to the length of registry keys, I am going to do it in two lines. In the code that follows, I first store the path to the registry (using the HKLM Windows PowerShell drive) in a variable. Next, I use the Get-ChildItem cmdlet (dir is an alias) to list the registry profile keys:

$profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'

dir $profilelist

The following illustrates using these two commands, as well as shows the results on my Windows 7 laptop:

PS C:\> $profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'

PS C:\> dir $profilelist

 

    Hive: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList

 

SKC                  VC Name                                              Property

  0                    5 S-1-5-18                                             {Flags, State, RefCount, Sid...}

  0                    3 S-1-5-19                                             {ProfileImagePath, Flags, State}

  0                    3 S-1-5-20                                             {ProfileImagePath, Flags, State}

  0                    10 S-1-5-21-124525095-70825963       {ProfileImagePath, Flags, State, Sid...}

  0                    7 S-1-5-21-1266540227-3509270         {ProfileImagePath, Flags, State, Sid...}

  0                    8 S-1-5-21-1266540227-3509270         {ProfileImagePath, Flags, State, Sid...}

Now that I have a listing of the profiles on the machine, I need to expand a couple of properties, such as ProfileImagePath and Sid. This should be a simple matter of using the Get-ItemProperty cmdlet to retrieve the name property from the list above. When I do this, however, an error arises. The command and associated error are shown here.

Image of command and associated error

There are actually two problems going on here. The first is that what is displayed under the Name column in the default output from the Get-Childitem cmdlet is not the actual value stored in the actual name property. The second problem is that even if that were fixed, the value HKEY_LOCAL_MACHINE that the name property contains as part of the value is not the name of the Windows PowerShell drive used by the Get-ItemProperty cmdlet. I discovered this by piping the results of the Get-ChildItem command to the Format-List cmdlet (fl is an alias for Format-List) and analyzing the output. The following figure illustrates this process.

Image of result of piping Get-ChildItem to Format-List

From the results discovered via Format-List, I ascertain I need to use the pspath property because it includes the registry provider. I can then pipe the results to the Select-Object cmdlet, and choose the ProfileImagePath property and the Sid property. This code and associated output are shown here (the % character is an alias for the Foreach-Object cmdlet and Select is an alias for the Select-Object cmdlet):

PS C:\> $profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'

PS C:\> Get-childItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' | % {Get-ItemProperty $_.pspath } | Select profileImagePath, sid

 

ProfileImagePath                                                            Sid

C:\windows\system32\config\systemprofile                   {1, 1, 0, 0...}

C:\Windows\ServiceProfiles\LocalService

C:\Windows\ServiceProfiles\NetworkService

C:\Users\edwils                                                                {1, 5, 0, 0...}

C:\Users\UpdatusUser                                                     {1, 5, 0, 0...}

C:\Users\Administrator                                                    {1, 5, 0, 0...}

CW, that is an interesting excursion into working with the registry to retrieve user profile information. However, if it were me, I would just use Windows Management Instrumentation (WMI) instead. In fact, it returns even more information that is obtainable via the registry. Here is a simple command to return exactly the same information we just got from the registry (gwmi is an alias for the Get-WmiObject cmdlet):

gwmi win32_userprofile | select localpath, sid

The command and associated output are shown here:

PS C:\> gwmi win32_userprofile | select localpath, sid

localpath                                                              sid

---------                                                                ---

C:\Users\Administrator                                         S-1-5-21-1266540227-3509270964-2815946151-500

C:\Users\UpdatusUser                                           S-1-5-21-1266540227-3509270964-2815946151-1001

C:\Users\edwils                                                      S-1-5-21-124525095-708259637-1543119021-179756

C:\Windows\ServiceProfiles\NetworkService         S-1-5-20

C:\Windows\ServiceProfiles\LocalService               S-1-5-19

C:\windows\system32\config\systemprofile           S-1-5-18

If I run the WMI command with administrator rights, I can find the last time each profile was used. This information might be useful from a cleanup perspective. Here is the command to do that. Here are the aliases the following command uses:

PS C:\> Get-Alias gwmi, select, ft

 

CommandType              Name                           Definition

Alias                              gwmi                             Get-WmiObject

Alias                              select                             Select-Object

Alias                              ft                                    Format-Table

Here is the code to get the user profiles, convert the time from WMI format to a regular date time value, and display the path and SID:

gwmi win32_userprofile |

select @{LABEL="last used";EXPRESSION={$_.ConvertToDateTime($_.lastusetime)}},

LocalPath, SID | ft -a

 

CW, that is all there is to finding profiles on a computer. Join me tomorrow for more cool Windows PowerShell stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 


Use PowerShell to Easily Convert Decimal to Binary and Back

$
0
0

Summary: Learn how to use Windows PowerShell to easily convert decimal to binary and back, and simplify custom subnet mask calculations.

 

Microsoft Scripting Guy Ed Wilson here. Today is 63. Yep, that is right, today is 63. That is what you get when you see 11 11 11 represented as binary. This appears here.

32

16

8

4

2

1

1

1

1

1

1

1

 

PS C:\> 1+2+4+8+16+32

63

A long time ago in this same galaxy, I wrote a VBScript function that would accept a binary number such as 111111 and translate it to a decimal value. It was a fun exercise, but it was also a bit complex. I had to get the length of the string, break it down piece by piece by position, calculate the value of that position, keep a running total, and loop back again. Now, I could translate that VBScript into Windows PowerShell, but that would be both a waste of time as well as downright misleading. This is because in Windows PowerShell, I have direct access to the .NET Framework Convert class. The Convert class resides in the system namespace, and it contains a large number of static methods for converting one type of thing to another. Well, okay, it contains 25 static methods and properties (I can use the Get-Member cmdlet and the Measure-Object cmdlet to obtain this information):

PS C:\> [convert] | gm -s | measure

Count    : 25

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

The Convert class is well documented on MSDN, but in reality, the information obtained via the Get-Member cmdlet is usually enough information to make the conversion. For example, I can put the Convert class in square brackets, pipe it to Get-Member, use the static switch to retrieve static members, list the exact method I want to use, and send it to the Format-List cmdlet. This will show the method and all the associated overloads. This command sounds complicated, but it is really simple. The command is shown here:

[convert] | gm -s toint32 | fl *

The command and associated output are shown in the following figure.

Image of command and associated output

Hmm, it looks like there is a static method called ToInt32 that will accept a string value, so to use this method to convert a binary number, all I need to do is to pass the string and the number base (which is base 2 for the case of binary number conversion). The command shown here translates today’s date, 111111:

[convert]::ToInt32("111111",2)

The command and associated output are shown here.

Image of command and associated output

I can use the following script to translate a binary formatted subnet mask into decimal format:

ConvertBinarySubnetMaskToDecimal.ps1

$a=$i=$null

"11111111","11111111","11111000","00000000" |

 % {

     $i++

     [string]$a += [convert]::ToInt32($_,2)

     if($i -le 3) {[string]$a += "."}

   }

$a

ConvertBinarySubnetMaskToDecimal.ps1 demonstrates using the System.Convert .NET Framework class to convert from a binary number into decimal format. It uses a counter variable, $i, to keep track of decimal placement. As each number passes over the pipeline, I store the converted value in the $a variable. This script could easily be converted into a function if this were a task that is normally performed. The complete text of this script appears on the Scripting Guys Script Repository.

The flip side of the coin is translating a decimal number into binary format. To convert a decimal number into binary format, I once again use the Convert class, but this time, I use the ToString method. The syntax is similar to that of the ToInt32 method. The command shown here converts the decimal number 15 into a binary number.

[convert]::ToString(15,2)

The command and associated output appear here.

PS C:\> [convert]::ToString(15,2)

1111

After I know I can do that, I can also write a quick script to convert a decimal subnet mask to binary representation. This script is shown here:

ConvertDecimalSubnetMaskToBinary.ps1

$a=$i=$null

"255","255","128","0" |

 % {

     $i++

     [string]$a += [convert]::ToString([int32]$_,2)

     if($i -le 3) {[string]$a += "."}

   }

$a

ConvertDecimalSubnetMaskToBinary.ps1 demonstrates using the System.Convert .NET Framework class to convert from a decimal number into binary format. It uses a counter variable, $i, to keep track of decimal placement. As each number passes over the pipeline, I store the converted value in the $a variable. This script could easily be converted into a function if this were a task that is normally performed. The complete text of this script appears on the Scripting Guys Script Repository.

 

Join me tomorrow as I introduce Guest Blogger Chris Walker who will talk about using Windows PowerShell to manage SharePoint profiles. It is a really good article, and I am certain you will enjoy it. Until then, see ya.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Edit SharePoint Profiles

$
0
0

Summary: Microsoft Premier Field Engineer Chris Weaver shows how to use Windows PowerShell to edit SharePoint profiles.

Microsoft Scripting Guy Ed Wilson here. It is Guest Blogger Weekend. To start the weekend, we have Chris Weaver. The Scripting Wife and I recently met Chris in real life (IRL) at Geek Ready, a conference at which I was speaking. What can I say? Chris is cool! Here is his biography. 

 

Photo of Chris Weaver

I have been working at Microsoft since late 2008. During that time I have been an engineer within CSS, a SharePoint 2010 TAP, and most recently a dedicated premier field engineer working with several of our Premier customers to support their SharePoint infrastructure. I have been using Windows PowerShell for the last two years to simplify the administration and troubleshooting of SharePoint for my customers. I very recently started my own blog, which will look at issues with SharePoint and its supporting infrastructure.

I enjoy camping with my family and kite surfing in my spare time (yeah, right, who has any of that?). 

Note   The script for today’s blog article appears on the Scripting Guys Script Repository.

 

I am working with a customer to automate the installation and configuration of SharePoint Server 2010 using Windows PowerShell.

I would like to show you one of the functions that we are using to configure profiles after we have completed an import. The customer is highly regulated and has users that cannot communicate in any form with other users, so when it was discovered that profiles by default would send email for several different reasons, we had to reverse it.

We will be editing the profile property called SPS-MailOptIn. In the GUI, it is represented by three check boxes. First, you want to load the following assembly so that we can work with the profiles:

Add-Type -Path "c:\program files\common files\microsoft shared\web server extensions\14\isapi\microsoft.office.server.dll"

And don’t forget to ensure that the SharePoint snap-in is loaded because we will want to use commands from it. You can use the following lines to verify or load that info. This snap-in is installed on all SharePoint installations and automatically loaded, if using the SharePoint 2010 Management Shell:

$PSSnapIn = Get-PSSnapin | where {$_.Name -like "*sharepoint*"}

 

if(!($PSSnapIn.Name -like "*sharepoint*"))

{

Add-PSSnapin Microsoft.SharePoint.PowerShell

}

Then we need to load the UserProfileManager, and I do so with the following code. To be able to manage this, the account running the script will need to be a User Profile Service Application Administrator with at least Manage Profile permissions:

$Site = Get-SPSite -Limit 1    #Get Site for Service context

$ServiceContext = Get-SPServiceContext($Site)

$ProfileManager = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($ServiceContext)

$Profiles = $ProfileManager.GetEnumerator()    #Load all profiles into array

Using Get-SPSite has two advantages. The first advantage is that I do not have to worry about disposing of the object because Windows PowerShell will do that for me. The second advantage is that by using the switch -Limit set to 1, I get one site to connect to and don’t have to worry about personalizing it to the farm before running the script.

Then I throw everything into a For loop, iterate through all the profiles, and get the property using the following:

$OriginalValue = $Profile["SPS-EmailOptin"].Value

After I get the value, I save it to a text file to ensure that I have it in the future, if needed. Then, as I had mentioned, only some users are regulated, so we compare the profiles name to a list of regulated users, and if not in the list, we proceed to change the value with the following line:

$Profile["SPS-EmailOptin"].Value = $EmailOptInValue

The variable $EmailOptInValue represents three integers. Each number can be 0 or 1. 0 means the box is checked, and 1 means it is not. As an example, the variable could be loaded with the 101 or 010:

  • The first digit is for “Notify me when someone leaves a note on my profile.”
  • The second digit is for “Notify me when someone adds me as a colleague.”
  • The third digit is for “Send me suggestions for new colleagues and keywords.”

Finally, I commit the change, which I have wrapped in an If statement. This allows me to have whatif functionality, which was important for my customer:

if(!($Whatif))

{

$Profile.Commit()

}

I hope this function helps you to improve your SharePoint environment through the use of Windows PowerShell.

 

Thanks, Chris, for writing this blog and sharing your script with us.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

 

 

Use PowerShell to Quickly Find Installed Software

$
0
0

Summary: Learn how to use Windows PowerShell to quickly find installed software on local and remote computers.

 

Microsoft Scripting Guy Ed Wilson here. Guest Blogger Weekend concludes with Marc Carter. The Scripting Wife and I were lucky enough to attend the first PowerShell User Group meeting in Corpus Christi, Texas. It was way cool, and both Marc and his wife Pam are terrific hosts. Here is what Marc has to say about himself.

I am currently a senior systems administrator with the Department of the Army. I started in the IT industry in 1996 with DOS and various flavors of *NIX. I was introduced to VBScript in 2000, and scripting became a regular obsession sometime in 2005. In 2008, I made the move to Windows PowerShell and have never looked back. My daily responsibilities keep me involved with Active Directory, supporting Microsoft Exchange, SharePoint, and various ASP.NET applications. In 2011, I founded the Corpus Christi PowerShell User Group and try to help bring others up to speed on Windows PowerShell. 

Take it away, Marc!

 

One of the life lessons I have learned over the years working in the IT field as a server administrator is that there are often several different valid responses to a situation. It's one of the things that makes work interesting. Finding the "best" solution to a problem is one of the goals that I think drives many people who are successful at what they do. Occasionally, the best solution is the path of least resistance.

This is one things I love most about working with Windows PowerShell (and scripting in general) is that most problems have more than one solution. Sometimes the "right" way to do something comes down to a matter of opinion or preference. However, sometimes the best solution is dictated by the environment or requirements you are working with.

For instance, let us talk about the task of determining which applications are installed on a system. If you're familiar with the Windows Management Instrumentation (WMI) classes and the wealth of information that can be gathered by utilizing the Get-WmiObject cmdlet, an obvious choice might be referencing the Win32_product class. The Win32_Product represents products as they are installed by Windows Installer. It is a prime example of many of the benefits of WMI. It contains several useful methods and a variety of properties. At first glance, Win32_Product would appear to be one of those best solutions in the path of least resistance scenario. A simple command to query Win32_Product with the associated output is shown in the following image.

Image of command to query Win32_Product

The benefits of this approach are:

  • This is a simple and straightforward query: Get-WmiObject -Class Win32_Product.
  • It has a high level of detail (for example, Caption, InstallDate, InstallSource, PackageName, Vendor, Version, and so on).

However, because we are talking about alternative routes, let us look at another way to get us to arrive at the same location before we burst the bubble on Win32_Product. Remember, we are simply looking for what has been installed on our systems, and because we have been dealing with WMI, let’s stay with Get-WmiObject, but look at a nonstandard class, Win32Reg_AddRemovePrograms

What is great about Win32Reg_AddRemovePrograms is that it contains similar properties and returns results noticeably quicker than Win32_Product. The command to use this class is shown in the following figure.

Image of command to use Win32Reg_AddRemovePrograms

Unfortunately, as seen in the preceding figure, Win32Reg_AddRemovePrograms is not a standard Windows class. This WMI class is only loaded during the installation of an SMS/SCCM client. In the example above, running this on my home laptop, you will see the "Invalid class" error if you try querying against it without an SMS/SCCM client installation. It is possible (as Windows PowerShell MVP Marc van Orsouw points out) to add additional keys to WMI using the Registry Provider, and mimic what SMS/SCCM does behind the scenes. Nevertheless, let us save that for another discussion.

One other possibly less obvious and slightly more complicated option is diving into the registry. Obviously, monkeying with the registry is not always an IT pro's first choice because it is sometimes associated with global warming. However, we are just going to query for values and enumerate subkeys. So let's spend a few moments looking at a method of determining which applications are installed courtesy of another Windows PowerShell MVP and Honorary Scripting Guy Sean Kearney (EnergizedTech). In a script that Sean uploaded to the Microsoft TechNet Script Center Repository, Sean references a technique to enumerate through the registry where the "Currently installed programs" list from the Add or Remove Programs tool stores all of the Windows-compatible programs that have an uninstall program. The key referred to is HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall. The script and associated output are shown in the following figure.

Image of script and associated output

Here are the various registry keys:

#Define the variable to hold the location of Currently Installed Programs
  $UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" 
#Create an instance of the Registry Object and open the HKLM base key
   $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername) 
#Drill down into the Uninstall key using the OpenSubKey Method
  $regkey=$reg.OpenSubKey($UninstallKey) 
#Retrieve an array of string that contain all the subkey names
  $subkeys=$regkey.GetSubKeyNames() 
#Open each Subkey and use the GetValue Method to return the string value for DisplayName for each

At this point, if you are anything like me, you are probably thinking, "I'll stick with a one-liner and use Win32_Product.” But this brings us back to why we started looking at alternatives in the first place. As it turns out, the action of querying Win32_Product has the potential to cause some havoc on your systems. Here is the essence of KB974524.

The Win32_product class is not query optimized. Queries such as “select * from Win32_Product where (name like 'Sniffer%')” require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause:,

  • This process initiates a consistency check of packages installed, and then verifying and repairing the installations.
  • If you have an application that makes use of the Win32_Product class, you should contact the vendor to get an updated version that does not use this class.

On Windows Server 2003, Windows Vista, and newer operating systems, querying Win32_Product will trigger Windows Installer to perform a consistency check to verify the health of the application. This consistency check could cause a repair installation to occur. You can confirm this by checking the Windows Application Event log. You will see the following events each time the class is queried and for each product installed:

Event ID: 1035
Description: Windows Installer reconfigured the product. Product Name: <ProductName>. Product Version: <VersionNumber>. Product Language: <languageID>. Reconfiguration success or error status: 0.

Image of Event ID 1035

 

Event ID: 7035/7036
Description: The Windows Installer service entered the running state.

Image of Event ID 7036

 

Windows Installer iterates through each of the installed applications, checks for changes, and takes action accordingly. This would not a terrible thing to do in your dev or test environment. However, I would not recommend querying Win32_Product in your production environment unless you are in a maintenance window. 

So what is the best solution to determine installed applications? For me, it is reading from the registry as it involves less risk of invoking changes to our production environment. In addition, because I prefer working with the ISE environment, I have a modified version of Sean’s script that I store in a central location and refer back to whenever I need an updated list of installed applications on our servers. The script points to a CSV file that I keep up to date with a list of servers from our domain. 

$computers = Import-Csv "D:\PowerShell\computerlist.csv"

$array = @()

foreach($pc in $computers){

    $computername=$pc.computername

    #Define the variable to hold the location of Currently Installed Programs

    $UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" 

    #Create an instance of the Registry Object and open the HKLM base key

    $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername) 

    #Drill down into the Uninstall key using the OpenSubKey Method

    $regkey=$reg.OpenSubKey($UninstallKey) 

    #Retrieve an array of string that contain all the subkey names

    $subkeys=$regkey.GetSubKeyNames() 

    #Open each Subkey and use GetValue Method to return the required values for each

    foreach($key in $subkeys){

        $thisKey=$UninstallKey+"\\"+$key 

        $thisSubKey=$reg.OpenSubKey($thisKey) 

        $obj = New-Object PSObject

        $obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername

        $obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))

        $obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))

        $obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))

        $obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))

        $array += $obj

    } 

}

$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | ft -auto

 

My modified version of Sean’s script creates a PSObject to hold the properties I am returning from each registry query, which then get dumped into an array for later use. When I am done, I simply output the array and pass it through a Where-Object to display only those entries with something in the DisplayName. This is handy because I can then refer back to just the array if I need to supply different output. Say I want to only report on a specific server. I’d change Where-Object to something like this:

Where-Object { $_.DisplayName -and $_.computerName -eq “thisComputer”} 

In conclusion, if you have added Windows PowerShell to your IT tool belt, you have plenty of go-to options when someone asks you, “What’s the best solution to a problem?”

 

Thank you, Marc, for writing this post and sharing with our readers

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

 

 

Use Custom Views from Windows Event Viewer in PowerShell

$
0
0

Summary: Learn how to use Event Viewer custom views in Windows PowerShell to parse event logs quickly.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I love Windows 7. It absolutely rocks! One of the things I love about Windows 7, in addition to Windows PowerShell, is the new Event Viewer. I have created a custom view in my Event Viewer. I exported that custom view, and when I try to use it in the Get-WinEvent cmdlet, it fails. Can you help me? I would love to be able to use Windows PowerShell to parse my custom view of the event logs. I know you can do this because you are the greatest!

—LD

 

Hey, Scripting Guy! AnswerHello LD,

Microsoft Scripting Guy Ed Wilson here. I agree with you, at least on two counts. I also love Windows PowerShell 7 and the new Event Viewer. The Windows PowerShell Get-WinEvent cmdlet is also very powerful, and provides lots of opportunities for experimentation. I have written many articles about using the Get-WinEvent cmdlet on the Hey, Scripting Guy! Blog.

So, let’s see what exactly you are talking about when it comes to exporting a custom view from the Event Viewer application. As shown in the following figure, when I open the Event Viewer, the top portion in the upper left section of the screen contains Custom Views.

Image of Custom Views in Event Viewer

To create a custom view, I select Create Custom View from the Action pane and the Create Custom View interface is displayed. This dialog box is shown in the following figure.

Image of Create Custom View dialog box

After I save the custom view, I can export it to XML by selecting the custom view, and clicking Export Custom View in the Action menu. This technique works great for exporting custom event log views either for backup purposes, or to use on other computers via the Event Viewer application. Unfortunately, it does not work when I attempt to import it via the Get-WinEvent cmdlet:

Get-WinEvent -FilterXml ([xml](Get-Content C:\fso\exportedCustomView.xml))

The command and associated error are shown in the following figure.

Image of command and associated error

The reason the error is generated is because Export Custom View includes additional information required by the Event Viewer to create and host the custom event view. What the Get-WinEvent cmdlet requires is the <QueryList> information.

To find the <QueryList> information, I click Filter Current Custom View in the Action menu. When the Filter Current Custom View dialog box appears, I click the XML tab. This displays the <QueryList> information, as shown in the following figure.

Image of <QueryList> information

There is no copy button, even if you select the Edit query manually check box. But I can easily highlight everything with my mouse, and press Ctrl+C to copy the selection to the Clipboard. After I have copied the information to the Clipboard, I create a new text file, paste the contents, and save it with a .xml file extension. The following figure shows the contents of the custom event log view.

Image of contents of custom event log view

After I have only the <QueryList> information in a text file, I can now use the exact same command I used previously, but this time it works. The command I use is shown here:

Get-WinEvent -FilterXml ([xml](Get-Content C:\fso\Past24CustomView.xml))

The command and associated output are shown in the following figure.

Image of command and associated output

Here are the steps I use:

  1. Create a custom view in the Event Viewer utility.
  2. Display the <QueryList> information from the custom view by clicking Filter Custom View from in the Action menu.
  3. Click the XML tab.
  4. Highlight the <QueryList> information with your mouse, and press Ctrl+C to copy the <QueryList> data to the Clipboard.
  5. Open Notepad and paste the information from the Clipboard into the new text file.
  6. Save the file with a .xml file extension.
  7. Use the Get-Content cmdlet to read the contents of the XML file.
  8. Cast the returned data to {XML] type and pass it to the FilterXML parameter of the Get-WinEvent cmdlet.

That is it. It seems like a lot of steps, but they are pretty logical. In addition, this provides an excellent way to process data quickly from multiple event logs.

LD, that is all there is to using a custom view from Event Viewer in a Windows PowerShell cmdlet. Join me tomorrow for more exciting Windows PowerShell tricks.

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

 

See Why PowerShell Can't Export Some Properties to CSV

$
0
0

Summary: Learn why Windows PowerShell cannot export certain properties to a CSV file and what to do about it.

 

Microsoft Scripting Guy, Ed Wilson, is here. The other day, Terry Dow posted a comment on my Use PowerShell to Work with CSV Formatted Text blog post. Basically, Terry is asking about exporting a multivalue property via Export-CSV file. I can illustrate the problem by using the Get-Process cmdlet to retrieve information about my currently running Outlook process. In this command, I pipe the results of the Get-Process cmdlet to the Format-List cmdlet. Here is the command:

Get-Process outlook | format-list *

The output is really extensive, and I am going to point out that one line appears like the one seen here:

Threads                    : {7148, 8120, 5320, 3080...}

I am now going to select the name and the threads properties, and pipe them to the Format-List cmdlet:

Get-Process outlook | format-list name, threads

The output does not contain all of the thread information. In fact, it only has a few numbers, followed by an ellipsis. I try several combinations, but nothing other than a few numbers and ellipses ever appear. This is shown in the following figure.

Image of unsatisfactory output

This behavior carries over when piping information to Export-CSV. In the following command I use the Get-Process cmdlet to retrieve the Outlook process. I pipe the resultant Process object to the Select-Object cmdlet (select is an alias) and retrieve the name and the threads, and pipe to Export-CSV to create a CSV file. I then open the file in Microsoft Excel.

Get-Process outlook | Select name, threads | export-CSV c:\fso\olproc.csv; c:\fso\olproc.csv

The resultant Microsoft Excel spreadsheet appears in the following figure (I highlighted the threads collection manually).

Image of resultant Excel spreadsheet

So, we have confirmed that for certain types of properties, Windows PowerShell does not display all the information. We seem to be a bit better off when using Format-Table or Format-List (but that is just an illusion).

The first thing we need to do is to see which type of object we are dealing with. To do this, I use the Get-Member cmdlet. This command is shown here:

PS C:\> Get-Process outlook | Get-Member threads

 

   TypeName: System.Diagnostics.Process

 

Name                           MemberType                                                     Definition

Threads Property           System.Diagnostics.ProcessThreadCollection         Threads {get;}

 

The threads property returns a ProcessThreadCollection. This class is documented on MSDN, but I do not need to really look at that article because I can find out what I need via the Get-Member cmdlet. The big thing to keep in mind is that a ProcessThreadCollection class is a collection of something else. This something else is a ProcessThread. The ProcessThread class is also documented on MSDN, and it is more interesting than the ProcessThreadCollection. I can find the information about the ProcessThread class by using the following command (gm is an alias for the Get-Member cmdlet):

(Get-Process outlook).threads | gm

The command and associated output are shown here:

PS C:\> (Get-Process outlook).threads | gm

 

   TypeName: System.Diagnostics.ProcessThread

 

Name                             MemberType                             Definition

Disposed                       Event                                        System.EventHandler Disposed(System.Object, System.EventArgs)

CreateObjRef                 Method                                     System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)

Dispose                         Method                                     System.Void Dispose()

Equals                           Method                                     bool Equals(System.Object obj)

GetHashCode                Method                                     int GetHashCode()

GetLifetimeService          Method                                     System.Object GetLifetimeService()

GetType                          Method                                     type GetType()

InitializeLifetimeService   Method                                  System.Object InitializeLifetimeService()

ResetIdealProcessor      Method                                  System.Void ResetIdealProcessor()

ToString                            Method                                     string ToString()

BasePriority                     Property                                    System.Int32 BasePriority {get;}

Container                        Property                                    System.ComponentModel.IContainer Container {get;}

CurrentPriority               Property                                    System.Int32 CurrentPriority {get;}

Id                                      Property                                    System.Int32 Id {get;}

IdealProcessor               Property                                    System.Int32 IdealProcessor {set;}

PriorityBoostEnabled      Property                                    System.Boolean PriorityBoostEnabled {get;set;}

PriorityLevel                   Property                                    System.Diagnostics.ThreadPriorityLevel PriorityLevel {get;set;}

PrivilegedProcessorTime Property                                    System.TimeSpan PrivilegedProcessorTime {get;}

ProcessorAffinity            Property                                    System.IntPtr ProcessorAffinity {set;}

Site                               Property                                    System.ComponentModel.ISite Site {get;set;}

StartAddress                  Property                                    System.IntPtr StartAddress {get;}

StartTime                      Property                                    System.DateTime StartTime {get;}

ThreadState                   Property                                    System.Diagnostics.ThreadState ThreadState {get;}

TotalProcessorTime        Property                                    System.TimeSpan TotalProcessorTime {get;}

UserProcessorTime         Property                                    System.TimeSpan UserProcessorTime {get;}

WaitReason                   Property                                    System.Diagnostics.ThreadWaitReason WaitReason {get;}

 

I can write the threads to a CSV file. To do this, I can use the Select-Object cmdlet and pipe the results to the Export-CSV file. This command is shown here (I use Select as an alias for the Select-Object cmdlet, and I open the CSV in Microsoft Excel by using the path to the newly created CSV file):

Get-Process outlook | Select -ExpandProperty threads | Export-CSV c:\fso\OlThread.csv;c:\fso\olthread.csv

The newly created CSV file opens easily in Microsoft Excel. This allows me to examine the various threads in Microsoft Outlook (they are all waiting by the way).

Image of newly created CSV file opened in Excel

So the solution to having an object name showing up in the CSV file is to use the Select-Object cmdlet to expand the object contained inside the property. Keep in mind that this will not enable you to be able to have the process name, processID, and threads in the same CSV file because of the way Select-Object works when expanding processes.

Use Export-CliXML to maintain fidelity to the original object. The following code illustrates this technique:

Get-Process outlook | Export-Clixml c:\fso\olXML.xml

To work with the object, use the Import-Clixml cmdlet as illustrated here where I import the XML file, and access the name and the process ID.

$a = Import-Clixml C:\fso\olXML.xml

$a.Name

$a.Id

I get all of the thread information by accessing the threads property. In the following example, I truncate the output:

PS C:\> $a.threads 

 

BasePriority            : 8

CurrentPriority         : 13

Id                      : 7148

PriorityBoostEnabled    : True

PriorityLevel           : Normal

<***OUTPUT TRUNCATED***>

 

Remember back at the beginning of the article when I was talking about the ProcessThreadCollection class? I said the ProcessThreadCollection class was not so interesting. Well, there is one interesting property, and it is the count property. I can use dotted notation to access it, as illustrated here:

PS C:\> $a.threads.count

39

Knowing the number of threads a process uses is valuable information. In fact, using the technique above, I can add it to an object by using the following technique (gps is an alias for the Get-Process cmdlet and select is an alias for Select-Object):

gps | select name, @{LABEL='number of threads';EXPRESSION= {$_.threads.count}}

This code works, but the number of threads is spread out everywhere (not sorted). To sort the number of threads, I pipe the command to the Sort-Object cmdlet. My label, number of threads, is the actual property name in my newly created custom object. This command is shown here:

gps | select name, @{LABEL='number of threads';EXPRESSION= {$_.threads.count}} | sort 'number of threads'

This works great, but now the information spreads to both sides of the Windows PowerShell console window. Now, I will use the Format-Table cmdlet (ft is an alias and a is short for autosize).

gps | select name, @{LABEL='number of threads';EXPRESSION= {$_.threads.count}} | sort 'number of threads' | ft –a

The command and associated output are shown in the following figure.

Image of command and associated output

Of course, it would be possible to pipe this type of expression to Export-CSV. I hope you enjoyed today’s excursions into more fun with CSV files.

Join me tomorrow for more cool Windows PowerShell stuff.

 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

 

Viewing all 3333 articles
Browse latest View live