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

Use Select-String Cmdlet in PowerShell to View Contents of Log File

0
0

Summary: Use the Windows PowerShell cmdlet, Select-String, to view the contents of a log file.

Hey, Scripting Guy! Question Hey, Scripting Guy! I have a log file that I created by dumping process information from Get-Process. It is quite long, and I am trying to use Select-String to find the number of instances of a certain process. The problem is that when I find the instance, I do not know what the columns mean. I am wondering what I can do to get the column headers in addition to the process information to show up in my output. I know I can do this with a script, but I don’t want to write a script to accomplish this task. Can you help?

—BU

Hey, Scripting Guy! Answer Hello BU,

Microsoft Scripting Guy, Ed Wilson, is here. Well, I am sore again. It seems that when I go see my trainer, I come away sore. After the weekend, I start to feel good, and then BOOM!—I go back and I am sore all over again. And I do mean sore all over.

Anyway, the Scripting Wife bought me a nice one-pound bag of English Breakfast tea, so I am sitting here, trying to cool down, sipping a cup of English Breakfast tea with a cinnamon stick in it, and thinking about rejoining the world of the living. I am checking my email sent to scripter@microsoft.com, and BU, I ran across your email. The answer is, "Sure, it can be done. In fact, it is not too hard at all."

First the log file

You say that you have a log file you created by using Get-Process. I am assuming the command you used was something like the following:

Get-Process >> C:\FSO|MyProcesses.txt

The resulting log file is shwon here:

Image of command output

If I use the Select-String cmdlet to read the log file and to look for instances related to the iexplore process, I might use a command such as this:

Get-Content C:\fso\myprocesses.txt | Select-String 'iexplore'

The command and the output from the command are shown here:

Image of command output

The problem with this command, is that I cannot tell what the columns of numbers mean. Also, it is more work than is required. I do not need to use Get-Content first. I can simply use Select-String to find the information I need.

One way to get column headings

I can use two commands to get the column headings. The first is to use the Get-Content cmdlet and return only the first two lines from the file. This will give me the column headings. The command is shown here:

Get-Content C:\fso\myprocesses.txt -TotalCount 2

I can then use the previous command to display the column details, as shown here:

Image of command output

It is not ideal, but it does give me an idea of what is going on, and I can line up the column headings well enough to decipher the output. So, this will work.

Simplify things

I said that I do not need to resort to Get-Content at all. In fact, I can use Select-String to parse the file and find the information all at the same time. To do this, all I need to do is to specify the path to the file, for example:

Select-String -Path C:\fso\myprocesses.txt -Pattern iexplore

The command and the output are shown here (note that this command includes the file and the line number where the match occurred).

Image of command output

I still need to obtain the headers from the file to be able to make sense of the output. I could go back to using Get-Content, but the output would not be quite as readable. A better way is to use the regular expression OR pattern. I know that one of the columns includes the word Handles. So I can specify my pattern to be iexplore OR Handles. Here is the command I use:

Select-String -Path C:\fso\myprocesses.txt -Pattern "(iexplore|Handles)"

The command and the output are shown here:

Image of command output

The output is a little better, and the columns line up pretty well. I may decide to leave it at this. But if I do not need the Line number field or the Path field, I can clean up the output. To do this, I need to know the properties of the MatchInfo object. I get these from Get-Member as shown here:

Select-String -Path C:\fso\myprocesses.txt -Pattern "(iexplore|Handles)" | get-member -MemberType Properties

 

   TypeName: Microsoft.PowerShell.Commands.MatchInfo

 

Name       MemberType Definition

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

Context    Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}

Filename   Property   string Filename {get;}

IgnoreCase Property   bool IgnoreCase {get;set;}

Line       Property   string Line {get;set;}

LineNumber Property   int LineNumber {get;set;}

Matches    Property   System.Text.RegularExpressions.Match[] Matches {get;set;}

Path       Property   string Path {get;set;}

Pattern    Property   string Pattern {get;set;}

Luckily, the property names make sense. Obviously, Pattern is the pattern I specified to find the matches. The LineNumber and Path properties are the file and the line number in the file. So I want the Line property. Here is my revised command:

Select-String -Path C:\fso\myprocesses.txt -Pattern "(iexplore|Handles)" | select line

Here is the command and the revised output. It is quite readable now.

Image of command output

BU, that is all there is to using Select-String. Join me tomorrow when I will talk about more way 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 


PowerTip: Use PowerShell and Graphical Tool to View Files

0
0

Summary: Use Windows PowerShell and a graphical tool to view large files.

Hey, Scripting Guy! Question How can I use Windows PowerShell to easily see what files in a folder are the largest so I can clean up my file system?

 Hey, Scripting Guy! Answer Use the Get-ChildItem cmdlet to return the contents of a folder, select the name and length of the files, then
            pipe the results to the Out-GridView cmdlet for easy viewing. This example uses a folder named C:\fso:

Get-ChildItem c:\fso | Select name, length | Out-GridView 


Analyzing PowerShell Script Files

0
0

Summary: Learn how to analyze Windows PowerShell script files.

Microsoft Scripting Guy, Ed Wilson, is here. This morning I am sitting around sipping a cup of English Breakfast tea. I added some peppermint, spearmint, lemon grass, licorice root, and a cinnamon stick to the pot. The result is a very refreshing cup of tea. I am sitting on the back porch with my Surface 3 Pro and playing around with Windows PowerShell.

I have a folder that I use to store miscellaneous Windows PowerShell scripts. I call the folder PSExtras and it contains scripts that I wrote for no apparent reason—I was just playing around. I decided that I would look at some of these scripts to determine a few things about the way I write scripts. To be fair, I have not added much to this folder in the last several years. Therefore, the folder is really more a picture of the way I used to write Windows PowerShell scripts three to four years ago.

One of the cool things about the Select-String cmdlet is that not only will it parse a file, but it will also parse all files in a folder. For example, if I want to see the files where I used the Write-Host cmdlet, all I need to do is write something like:

Select-String -Path E:\Data\PSExtras\*.ps1 -Pattern 'write-host' -List | select filename, line –Unique

The output produces a list of the file names and the line where the Write-Host cmdlet appeared in the script.

These days, the main reason I use the Write-Host cmdlet is because I want to display output in a different color. I always use the –ForeGround parameter when I do this. So, in how many of my scripts did I do this in my PSExtras folder? Here is the answer (this is a one-line command that is broken to fit on the website):

PS C:\> Select-String -Path E:\Data\PSExtras\*.ps1 -Pattern 'write-host -fore'  |
select filename, line | sort filename -Unique | measure

 

Count    : 204

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

How many scripts did I write where I did not use the –Foreground parameter? I can easily get that by removing the –fore from my pattern. Here is the result:

PS C:\> Select-String -Path E:\Data\PSExtras\*.ps1 -Pattern 'write-host'  |
select filename, line | sort filename -Unique | measure

 

Count    : 375

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

But wait! That is all the files that include Write-Host. So the answer is 375-204, which is 171 scripts that did not include a directive to change the foreground color. Today I would not do that.

What about the length of these scripts? I can get this information by using the Measure-Object cmdlet.

To do this, I use the Get-ChildItem cmdlet to find all of my .ps1 script files. Then for each of them, I get the content of the file and pipe it to the Measure-Object cmdlet where I choose the Line property. Now I pipe that to the Measure-Object cmdlet, and I choose the Average, Maximum, and Minimum properties. Here is the command (using aliases) and the output from the command:

PS C:\> Gci E:\Data\PSExtras\*.ps1 | % {get-content $_.fullname | measure -Line } |
measure -Property lines -Average -Maximum -Minimum

 

Count    : 1347

Average  : 35.7995545657016

Sum      :

Maximum  : 698

Minimum  : 1

Property : Lines

This tells me that I have 1347 scripts in my PSExtras folder, and that the average length is 35 lines (this also includes comments and blank lines). The maximum length of my scripts is 698 lines, and the shortest one (no surprise) is a one-liner.

Most of the time, I do not save one-liners in files because I can easily re-create them (this is usually quicker than it takes me to actually locate the script file that contains the command), so this is not necessarily representative of my Windows PowerShell usage. But, I do think it is kind of interesting.

How does this compare with the VBScript files I wrote over the years? I also have a VBSExtras folder, so let's find out:

PS C:\> Gci E:\Data\VBSExtras\*.vbs | % {get-content $_.fullname | measure -Line } |
measure -Property lines -Average -Maximum -Minimum

 

Count    : 1610

Average  : 48.2391304347826

Sum      :

Maximum  : 1846

Minimum  : 0

Property : Lines

Wow! The average file length was 48 lines long, and my longest VBScript is 1846 lines long. Note that I also have more VBScript files than I have Windows PowerShell files in my *Extras folder. This is probably because of the fact that I do not save all my one-liners.

Here is an image that shows the two commands and their associated output:

Image of command output

That is all there is to using Select-String and working with files. Join me tomorrow when I will talk about more cool 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 

PowerTip: Use PowerShell to Assess Memory Requirements

0
0

Summary: Use Windows PowerShell to assess memory requirements.

Hey, Scripting Guy! Question How can I use Windows PowerShell to figure out the minimum, maximum, and average working set
           requirements for processes on my workstation?

Hey, Scripting Guy! Answer Use the Get-Process cmdlet to return the working set information. Select the working set and pipe the results
           to Measure-Object. Here is an example:

Get-Process | measure ws -Maximum -Minimum -Average

What's Up with the Crazy PowerShell Formatting?

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about calculating properties in output.

Hey, Scripting Guy! Question Hey, Scripting Guy! Dude, I’ve got a question. I read your Hey, Scripting Guy! Blog every day. In fact, most times, I read it twice a day. One thing you do all the time, and I don’t understand, is that you send something to, like, Format-Table. I get it when you put in a property name—hey, that’s easy. What I don’t understand is when you add the curly braces, the ampersands, and all that, and then the output is magically changed into what you want. How does that work? I can’t seem to find where that is even documented. Is this secret Scripting Guy stuff, or can you share?

—TW

Hey, Scripting Guy! Answer Hello TW,

Microsoft Scripting Guy, Ed Wilson, is here. Today is the Scripting Wife’s birthday (and she figured I would forget). If you are on Twitter, you might want to wish @ScriptingWife a happy birthday. This morning I decided to make a pot of Oolong tea to go with my breakfast. Normally, I drink green tea in the afternoon or late morning, but I decided to do something daring this morning. I am still drinking the very good Oolong tea I brought back from Germany, so I did not put much in it—a bit of jasmine flowers, a cinnamon stick, and a bit of lemon grass. The flavor is light and delicate, and it actually compliments my fresh kiwi quite well. I was reading over email sent to scripter@microsoft.com when I ran across this email from TW.

I know exactly what you mean. Calculating a value to display for a property is way cool, very powerful, and confusing as all get out...until you know what is going on. When you know the secret, it is quite easy. So, let’s get started.

It all begins with a hash table

Let me make this one thing perfectly clear—as someone once said. If you do Windows PowerShell, and if you want to get beyond the very basics, you need to understand about hash tables. This is because Windows PowerShell loves hash tables, and we use them everywhere.

So what is a hash table? I could say it is a lot like a dictionary object, and if you used VBScript or some other language in the past, you might say, “Oh, yeah.” Otherwise, that is sort of like saying an aardvark is kind of like a platypus (or not, as the case may be).

A hash table lets me create key/value pairs. I can have more than one of these pairs. Windows PowerShell uses hash tables as a way to quickly provide information about a variety of things. To create a hash table, I use an at sig ( @ ) and a pair of curly brackets (a script block). Inside this, I add my key/value pairs. Each of these pairs is separated by semicolons. Here is a basic example:

PS C:\> @{a=1;b=2;c=3}

 

Name                           Value                                          

----                           -----                                          

c                              3                                              

b                              2                                              

a                              1                      

You will notice that I do not even have to store it in a variable. When I type the hash table and press ENTER, it is sent to the default outputter, which in this case, is the console. If I had a pipeline, it would go on down the pipeline for additional processing. Here is an example:

PS C:\> @{a=1;b=2;c=3} | format-list *

 

Name  : c

Key   : c

Value : 3

 

Name  : b

Key   : b

Value : 2

 

Name  : a

Key   : a

Value : 1

So a hash table is made up of a series of key/value pairs that are enclosed inside a pair of curly brackets and preceded by an at sign.

A special hash table

The Format-List, Format-Table, and Select-Object cmdlets accept a hash table for the –Property parameter. (I cannot use this technique with Format-Wide.) The syntax for each shows that the –Property parameter accepts an object, and a hash table is an object:

PS C:\> Get-command format-list, Format-Table, Select-Object -Syntax

 

Format-List [[-Property] <Object[]>] [-GroupBy <Object>] [-View <string>]

[-ShowError] [-DisplayError] [-Force] [-Expand <string>] [-InputObject

<psobject>] [<CommonParameters>]

 

Format-Table [[-Property] <Object[]>] [-AutoSize] [-HideTableHeaders] [-Wrap]

[-GroupBy <Object>] [-View <string>] [-ShowError] [-DisplayError] [-Force]

[-Expand <string>] [-InputObject <psobject>] [<CommonParameters>]

 

Select-Object [[-Property] <Object[]>] [-InputObject <psobject>]

[-ExcludeProperty <string[]>] [-ExpandProperty <string>] [-Unique] [-Last

<int>] [-First <int>] [-Skip <int>] [-Wait] [<CommonParameters>]

An example

For me, one of the things that is a bit difficult to read is lots and lots of bytes. I much prefer that they display as kilobytes, megabytes, gigabytes, or even terrabytes, than simply bytes. Windows PowerShell has "administrative constants" (KB, MB, GB, TB) that I can use to do these calculations easily. Here is an example:

PS C:\> 1kb

1024

 

PS C:\> 1mb

1048576

 

PS C:\> 1gb

1073741824

 

PS C:\> 1tb

1099511627776

When the virtual memory property displays in bytes, I like to do something about it, and this is an excellent opportunity to use the calculated property value technique. Often, I do this interactively at the Windows PowerShell console because I have gotten pretty good at this technique. But for clarity, I am going to show it in the Windows PowerShell ISE because it is a bit easier to see what is going on.

All I am going to do is grab process information by using the Get-Process cmdlet, and send it down the pipeline. First up, I will use Format-Table to display the virtual memory in megabytes, instead of bytes. Here is how I do it:

get-process |

format-table @{

    L='VM in Megs';

    E={$_.vm/1MB}

     }

Get-Process retrieves the process information and sends it down the pipeline. I send it to the Format-Table cmdlet. Now I open my hash table with the at sign and opening curly bracket as shown here:

get-process |

format-table @{

Now the L and the E in the command are abbreviations that stand for Label and for Expression. Because this is a special hash table, I can use the abbreviations if I wish. Remember, it is key name equals value. Each part is separated by a semicolon. So here, the Label is VM in Megs:

L='VM in Megs';

The Expression is a script block, and it is always a script block. Therefore, it has an opening and a closing curly brace. E is the abbreviation for Expression, and I set it equal to my script block value. Inside the script block, I use the $_ to represent the current item in the pipeline, and I divide it by the administrative constant, MB. I am using one megabyte to divide the current value in the pipeline. Here is that line:

E={$_.vm/1MB}

The last thing to do is close the curly brackets ( } ). 

Here is what it would look like with Format-List:

get-process |

format-list @{

    Label='VM in Megs';

    Expression={$_.vm/1MB}

     }

And here is what it would look like with Select-Object:

get-process |

Select-Object @{

    Label='VM in Megs';

    Expression={$_.vm/1MB}

     }

If I want additional properties, I add them to the script, either before or after the calculated value. (I generally do it before because I think it is easier to read.) Here is an example:

get-process |

Select-Object Name, @{

    Label='VM in Megs';

    Expression={$_.vm/1MB}

     }

Here is what that output looks like on my computer:

Image of command output

TW, that is all there is to using Windows PowerShell to calculate values for output. Join me tomorrow when I will talk about 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 

PowerTip: Find Current Working Directory in PowerShell

0
0

Summary: Easily find your current working directory in Windows PowerShell.

Hey, Scripting Guy! Question I saved a file to my current working directory while I was working in the Windows PowerShell console,
           but I am not sure how to find where the file is.

Hey, Scripting Guy! Answer Use the Convert-Path cmdlet and specify a period ( ). It displays a file system path to your
           current working directory:

Convert-Path.

Weekend Scripter: Why Does PowerShell Hide Stuff?

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about how Windows PowerShell decides what to display.

Microsoft Scripting Guy, Ed Wilson, is here. This morning I received an email from a friend with whom I used to work a long time ago. (He was one of the reviewers on the WMI book I wrote in the VBScript days.) Here is his email:

Hey, Scripting Guy! Question Hey, Mr. Ed! How are you and the Scripting Wife? Hope you are well! I enjoy seeing your Facebook posts about your antics with your personal trainer. I have a quick Windows PowerShell question for you that has been nagging me for a while. I have a simple script that runs via Group Policy at computer startup:

$computername = gc env:computername

$applications= Get-WmiObject Win32_ComputerSystem

$applications | out-file "\\server\share\computer\Win32_ComputerSystem\$computername.txt"

The output of this script contains only a few of the many properties of the Win32_ComputerSystem class. Specifically, they are Domain, Manufacturer, Model, Name, PrimaryOwnerName, and TotalPhysicalMemory.

But the Win32_ComputerSystem has lots more properties than that. When I type the following command, the *.csv file contains all of the properties:  

$applications | export-csv "\\server\share\computer\ComputerSystem\$computername.csv"

How does Out-File have the audacity to pick and choose which properties to write to the file, and how does it chose them? Is there a way to tell it to write all of the properties without having to hand pick them?

~Dave

Hey, Scripting Guy! Answer Here is my reply...

Hi Dave,

It’s great to hear from you. Out-File is simply redirecting a standard output stream. With many of the WMI classes, such as Win32_ComputerSystem, we have a Format*xml file that we created to select the main properties of interest from the WMI class. The file is called DotNetTypes.format.ps1xml and it is located in the $pshome directory (which normally resolves to C:\Windows\System32\WindowsPowerShell\v1.0). You can create your own Format*xml file and choose additional properties, but that is hard core.

Your variable, $applications, will hold the deserialized Win32_ComputerSystem object and permit you to call methods and access all of the properties.

But when you send it to the output formatter, it will use the format from the Format*xml file.This is the same as typing gwmi win32_computersystem at the Windows PowerShell console. Only the main properties display.

You can get around this by using Select-Object (or Format-List/Table) to choose *. This will retrieve all of the properties that are available from the Win32_ComputerSystem WMI class. Here is an example of how you might approach this:

gwmi win32_computersystem
$c = gwmi win32_computersystem
$c
$c | select *

Now you can redirect or use Out-File to send the returned information to a text file:

$c | select * >> c:\fso\c.txt

$c | select * | Out-File c:\fso\c.txt

By default, te reason you get the complete information is that Export-CSV is creating a deserialized object represented as a CSV file. You can even reconstitute the object by using Import-CSV. This is a different output stream.

To select specific properties going to Export-CSV, you can again use the Select object—but this time, only select the specific properties:

$c | select Domain, Name, Manufacturer, Model | Export-Csv c:\fso\c.csv

Hope all is well with you, and that this information helps clarify what you have been seeing. Stay in touch.

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 

PowerTip: Find all Format*xml Files Referencing WMI Class

0
0

Summary: Find all Format*xml files that contain references to specific WMI class.

Hey, Scripting Guy! Question I am trying to find why a particular WMI class returns a subset of properties. I suspect output is controlled by an
           entry in one of the Format*xml files, so how can I use Windows PowerShell to easily find which file contains
           the formatting information?

Hey, Scripting Guy! Answer Use the Get-ChildItem cmdlet to list the files in $pshome. Then use Select-String to search for the
           WMI class name. To clean up the output, select only the unique paths that return.
           Here is an example (this is a one-line command that has wrapped):

Select-String -Pattern win32_computersystem -Path ((ls $PSHOME).fullname)
-SimpleMatch -ea 0 | select-object path -Unique


Weekend Scripter: Sorting PowerShell Hash Tables

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about sorting hash tables.

Microsoft Scripting Guy, Ed Wilson, is here. Today is part of the Scripting Wife’s birthday weekend, and so we are heading out to see the Friesian horse keuring. She has watched the event via live streaming on the Internet, but never attended one in person…so off we go.

This means I am up a bit early, sipping extra strong English Breakfast tea, and munching on a kiwi. I do not have enough time to make my usual bowl of Irish steel-cut oats this morning. While I wait for my tea to steep, I thought I would answer a question that I have received several times over the last few days, "How do I sort a hash table in Windows PowerShell?"

Dude, what’s the problem?

Suppose I create a hash table and store it in a variable called $a. Here is such a hash table:

$a = @{a=1;b=2;c=3;d=4}

When I display the contents of the $a variable, I expect to see something like the following:

A 1

B 2

C 3

But unfortunately, this is not the case. Here is what actually happens:

PS C:\> $a

 

Name                           Value

----                           -----

c                              3

d                              4

b                              2

a                              1

As you can see, they are not in order. Hey, no problem. All I need to do is to pipe the hash table to the Sort-Object cmdlet, and all should be groovy. So I do the following:

PS C:\> $a | Sort-Object

 

Name                           Value

----                           -----

c                              3

d                              4

b                              2

a                              1

Bummer, that didn’t work. Maybe, I need to sort in a descending fashion. So I try the following:

PS C:\> $a | Sort-Object -Descending

 

Name                           Value

----                           -----

c                              3

d                              4

b                              2

a                              1

Dude, it still didn’t work. Maybe I need to specify that I want to sort on the Name property. So I do this:

PS C:\> $a | Sort-Object -Property name -Descending

 

Name                           Value

----                           -----

c                              3

d                              4

b                              2

a                              1

Nope, that didn’t work either. Maybe it is a bug?

So what is the problem?

I decide to use the Get-Member cmdlet to see if I can figure out what is happening. I take my last effort, add a pipeline character, and send the results to Get-Member. Here is what I get:

PS C:\> $a | Sort-Object -Property name -Descending | gm

 

   TypeName: System.Collections.Hash table

 

Name              MemberType            Definition

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

Add               Method                void Add(System.Object key, System.Object valu...

Clear             Method                void Clear(), void IDictionary.Clear()

Clone             Method                System.Object Clone(), System.Object ICloneabl...

Contains          Method                bool Contains(System.Object key), bool IDictio...

ContainsKey       Method                bool ContainsKey(System.Object key)

ContainsValue     Method                bool ContainsValue(System.Object value)

CopyTo            Method                void CopyTo(array array, int arrayIndex), void...

Equals            Method                bool Equals(System.Object obj)

GetEnumerator     Method                System.Collections.IDictionaryEnumerator GetEn...

GetHashCode       Method                int GetHashCode()

GetObjectData     Method                void GetObjectData(System.Runtime.Serializatio...

GetType           Method                type GetType()

OnDeserialization Method                void OnDeserialization(System.Object sender), ...

Remove            Method                void Remove(System.Object key), void IDictiona...

ToString          Method                string ToString()

Item              ParameterizedProperty System.Object Item(System.Object key) {get;set;}

Count             Property              int Count {get;}

IsFixedSize       Property              bool IsFixedSize {get;}

IsReadOnly        Property              bool IsReadOnly {get;}

IsSynchronized    Property              bool IsSynchronized {get;}

Keys              Property              System.Collections.ICollection Keys {get;}

SyncRoot          Property              System.Object SyncRoot {get;}

Values            Property              System.Collections.ICollection Values {get;}

I can see that I have a hash table object. Remember that Windows PowerShell pipelines objects. So what is happening is that Sort-Object is receive one big, fat, juicy hash table object, and there is nothing for it to sort.

The fix

What I need to do is to figure out a way to break down the big hash table object into individual entries. Then Sort-Object can sort by name or by value—whatever I specify. To do this, I use the GetEnumerator() method from the hash table object. This will basically permit my Sort-Object cmdlet to see the individual entries. Here is what I come up with:

PS C:\> $a.GetEnumerator() | sort -Property name

 

Name                           Value

----                           -----

a                              1

b                              2

c                              3

d                              4

Yea! It worked. What do I have now that Sort-Object is working with? I use Get-Member, and I can see that I now have DictionaryEntry objects. Here is the command:

PS C:\> $a.GetEnumerator() | sort -Property name | gm

 

   TypeName: System.Collections.DictionaryEntry

 

Name        MemberType    Definition

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

Name        AliasProperty Name = Key

Equals      Method        bool Equals(System.Object obj)

GetHashCode Method        int GetHashCode()

GetType     Method        type GetType()

ToString    Method        string ToString()

Key         Property      System.Object Key {get;set;}

Value       Property      System.Object Value {get;set;}

That is all there is to sorting hash tables. Looks like the Scripting Wife is ready to head out to the keuring. Have a great day, and I will talk to you tomorrow.

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 

PowerTip: Display Keys from Hash Table

0
0

Summary: Learn how to display keys from a hash table.

Hey, Scripting Guy! Question How can I display only the keys in a Windows PowerShell hash table?

Hey, Scripting Guy! Answer Store the hash table in a variable, and then use the Keys property, for example:

$a = @{a=1;b=2;c=3;d=4}

$a.Keys

PowerShell and Active Directory Recycle Bin

0
0

Summary: Guest blogger, Alan Kaplan, talks about using Windows PowerShell with the Active Directory Recycle Bin.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest blog post written by Alan Kaplan. I met Alan several years ago when the Scripting Wife and Jim Christopher started the Windows PowerShell User Group in Charlotte, North Carolina. Since then, our path continue to cross when I see him at various regional IT group meetings (and at PowerShell Saturday). He is a real Windows PowerShell enthusiast, and he has grown his skills significantly since I first met him. Now he is a guest blogger. Take it away Alan…

In March of 2013, during a presentation to the Carolina IT Professionals User Group in Charlotte, NC, Ed Wilson convinced me that it was time to switch from VBScript to Windows PowerShell. I answered a few questions correctly, and Ed gave me an autographed copy of Windows PowerShell 3.0 Step by Step. He suggested that I join the Charlotte PowerShell User Group, which he and Teresa (The Scripting Wife) attend as time permits. One and a half years later, I was at the Windows PowerShell meeting where I discussed an earlier version of this script. Ed asked if I would be willing to write a post for the Hey, Scripting Guy! Blog, and after clearing it with my employer’s lawyers, I said, “Yes.”

The Active Directory Recycle Bin was introduced in Windows Server 2008 R2. The Recycle Bin must be first be enabled, and the only way to restore a deleted a user account is to use the Restore-ADObject cmdlet, with pretty arcane parameters. My goal was to write a script which would:

  • Provide a GUI front end for Restore-ADObject
  • Allow the user to choose one or more user accounts to restore
  • Permit restoring from all domains within the forest for which an administrator has permissions

In the first version of this script, I used well-known GUIDs to refer to the location of the deleted object’s container and for the location of the user's container, which I chose to be the default location for the restored user object. (For more information about well-known GUIDs, see 6.1.1.4 Well-Known Objects.) This turned out to be unnecessary because Windows PowerShell makes getting this easy:

$oDomain = Get-ADDomain

$DeletedObjects = $oDomain.DeletedObjectsContainer

$UsersContainer = $oDomain.UsersContainer

These three lines got rid of about 30 lines of code between version 1 and version 2. But how can you get the information for another domain? The simple answer for all Active Directory commands for Windows PowerShell, is to use the -Server switch with the DNSDomain name instead of a true server name, for example:

$oDomain = Get-ADDomain –server corp.contoso.com

Undelete-User.ps1 lets you select the domain to search, and the destination for the restored user object. It defaults to running in test mode, where the changes are not committed to Active Directory. The maximum age of deleted objects is found inside the Configuration partition of the root forest domain. I capture that number with this line:

$MaxAge =Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$rootforestdomain"`

 -Partition "CN=Configuration,$rootforestdomain" -Properties msDS-DeletedObjectLifetime |

 select -ExpandProperty msDS-DeletedObjectLifetime

The script prompts you to choose how many days to go back in your search with $MaxAge as the default. You can choose to restore a single user by specifying the account SamAccountName. If you choose another domain, you are prompted to choose the global catalog server closest to you with the following:

  $DomainGCs = Get-ADDomainController -server $strDomain `

     -Filter {(Enabled -eq $true) -and (IsGlobalCatalog -eq $true)}

$strServer = $DomainGCs| select -Property hostname | out-gridview -Title "Select closest GC" -PassThru

When using Out-Gridview, it is important to remember to add –PassThru. Without it, nothing is returned by the cmdlet.

The query has been designed to work as fast as possible:

$users = Get-ADObject -searchbase $searchbase -Server $strServer -resultpagesize 100 `

  -Filter {(whenChanged -ge $changedDate) -and (Deleted -eq $true) -and (SamAccountName -like $strUser) } `

  -includeDeletedObjects -properties DisplayName, Description, SamAccountName, userprincipalname, DistinguishedName, WhenChanged

The default for $strUser is the asterisk ( * ). Because the user name is matched with the like operator, you can search for a single SamAccountName, or a list of names, such as SamMoor*. If you do nothing, the default applies and all accounts that were deleted during the time period are returned.

Instead of writing a log, I decided to use Start-Transcript and Stop-Transcript. Because transcription does not work inside the ISE, the script only offers a transcript when you are outside the ISE. The detection works like this:

if ( ($host.Name).Contains("ISE") -eq $False){

  $retval = [System.Windows.Forms.MessageBox]::Show("Would you like a transaction log of script activity?",`

    "Log", "YesNoCancel” , "Question” , "Button2")

The transcript logfile is sent by default the admin’s desktop. Note that I formatted the date inside of the conversion using ToString. Then the InputBox displays the log as the default:

      $strLog = "$env:UserProfile\Desktop\"+$(Get-Date).ToString("yyyyMMdd_HHmm")+"_UndeleteUserLog.txt"

      $strLog = [Microsoft.VisualBasic.Interaction]::InputBox("Log Path", "Path",$strLog)

The complete Windows PowerShell script is uploaded on the Script Center Repository: PowerShell Undelete User Script. The script is extensively commented. Please be a kind, gentle reader. I have only been using Windows PowerShell for a year and a half.

And now, a word from our lawyers: Alan Kaplan is an employee of the U.S. Department of Veteran’s Affairs. Any opinion expressed herein are his own, and not necessarily that of the Department.

Thank you so much for doing this, Alan—and for taking the time to share your experience with us today.

Join me tomorrow when I will have more way 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

PowerTip: Find Syntax for Related PowerShell Cmdlets

0
0

Summary: Easily find the syntax for related Windows PowerShell cmdlets.

Hey, Scripting Guy! Question How can I easily find the syntax for related Windows PowerShell cmdlets with the least effort?

Hey, Scripting Guy! Answer Use the Get-Command cmdlet, select the noun, and use the –syntax switch, for example:

Get-Command -Noun csv -Syntax

Use PowerShell to Create Ordered Dictionary

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to create an ordered dictionary.

Microsoft Scripting Guy, Ed Wilson, is here. One of the really cool things that was introduced in Windows PowerShell 3.0, is that I can create an ordered dictionary. Basically, an ordered dictionary works like a Windows PowerShell hash table. The difference is that it maintains its order.

Note  For a good overview of the ordered dictionary in Windows PowerShell, refer to this Hey, Scripting Guy! Blog post by June Blender: ConvertTo-OrderedDictionary.

Dude, it is frustrating…

One of the most frustrating things in the world is the way that nice, pretty, well-organized Windows PowerShell hash tables randomly seem to become jumbled up. Well, maybe it is not THE most frustrating thing in the world, but it is right up there with cold popcorn at a movie or a soggy sandwich on an airplane.

You don’t get it? Here is an example (a non-sorted hash table, not a soggy sandwich)...

I create a nice hash table, specify the letters in order, and boom! They become jumbled.

PS C:\> $d = @{a=28;b=29;c=30}

 

PS C:\> $d

 

Name                           Value                                           

----                           -----                                          

c                              30                                             

b                              29                                             

a                              28                 

Ordered dictionary objects to the rescue

Note  For some of the issues surrounding attempts to sort this type of hash table, see Weekend Scripter: Sorting PowerShell Hash Tables.

If you read this post, you know that it is possible to sort a hash table—it is, well, a bit of extra work. And the whole point of Windows PowerShell is to reduce work, not to make more work.

This is where the ordered dictionary objects come into play. The trick is that it must be done exactly right. Otherwise...well...if it is not done exactly right, it won’t work.

Inside scoop!  I will admit that the first time I tried to do this, it did not work. Even the second and third times, it did not work. But then I emailed some people on the Windows PowerShell team, and I was able to figure out what I was doing wrong.

This is how I tried to do it the first time...

I put the [ordered] type accelerator next to the variable I wanted to hold the ordered dictionary. It seemed to make sense.

[ordered]$o = @{a=1;b=2;c=3;d=4}

Here is the output from the command. Notice that it failed, and the error says the ordered attribute can only be specified on a hash literal node. Hmmm, that is about as clear as mud.

Image of command output

The fix is easy

When I was told I had the ordered attribute tag (type accelerator) in the wrong place, I moved it to the right place (both figuratively and literally). Voila! Now it works. Here is the corrected command:

$o = [ordered]@{a=1;b=2;c=3;d=4}

And as appears in the following image, the output is also in the correct order:

Image of command output

I pipe the output to the Get-Member cmdlet, and I can see that it is, in fact, an OrderedDictionary object:

Image of command output

As shown here, I can also retrieve associated key values in the same way that I can with a hash table:

Image of command output

But because it is an OrderedDictionary, I can also directly index into the object (like I can with an array). This is shown here:

$o = [ordered]@{a=1;b=2;c=3;d=4}

PS C:\> $o[1]

2

 

PS C:\> $o[3]

4

 

PS C:\> $o[0]

1

If I “accidently” created a hash table and I want to convert it to an ordered dictionary, or if I created an array and I want to convert it to an ordered dictionary, I can use June’s way cool ConvertTo-OrderedDictionary.ps1 script. In fact, it is so cool that I have it in a module in my profile. You can download the script from the Script Center Repository: ConvertTo-OrderedDictionary.

That is all there is to using an ordered dictionary. Join me tomorrow when I will talk about working with local user accounts.

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 

PowerTip: Retrieving Specific Items from Hash Table

0
0

Summary: Retrieve specific items from a Windows PowerShell hash table.

Hey, Scripting Guy! Question How can I use Windows PowerShell t retrieve specific values associated with specific keys in a hash table?

Hey, Scripting Guy! Answer Suppose you have a hash table such as this:

$d = @{a=28;b=29;c=30}

The keys and values associated with the hash table are:

PS C:\> $d

 

Name                           Value                                          

----                           -----                                          

c                              30                                             

b                              29                                             

a                              28          

You can retrieve by name (key) property by using the item method or by directly supplying the key.

  Note  Key names are letters, and to retrieve via the names, you must enclose the letters in quotation marks.

PS C:\> $d.Item('a')

28

 

PS C:\> $d['a']

28

Use PowerShell to Create Local User Accounts

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to create local user accounts.

Microsoft Scripting Guy, Ed Wilson, is here. As much as we might like to ignore it, or not do it, network administrators routinely need to create local user accounts on servers and on workstations. At times, these workstations or servers are even standalone, which means they are not joined to a domain.

As much as we might hate to admit it, there are still no Windows PowerShell cmdlets from Microsoft that permit creating local user accounts or local user groups. We finally have a Desired State Configuration (DSC ) provider that can do this—but to date, no cmdlets.

So what does this mean for a modern Windows PowerShell environment? It means that if I have Windows PowerShell 4.0, I can use the local user provider to create local user accounts, or I can go old school, and fall back on ADSI.

Note  I wrote a Local User Management Module that can be used to reduce the amount of code that needs to be created.

I wrote a blog post about creating local users several years ago, and I got comments like these:

  • “It does not work.”
  • “There is no method named Create.”

Keep in mind that this technique does work—but remember it is old school. This means that although the methods are there, they do not appear in the Windows PowerShell ISE or even show as available within the Windows PowerShell console. That is just some of the fun. One other thing to remember is that you must launch the Windows PowerShell console or the Windows PowerShell ISE with Admin rights if you expect this to work. Otherwise, you will get an “Access denied” error message.

Creating a local user account by using ADSI

To create a local user account, I can use the computer management console, go to Local Users and Groups, and navigate to Users. Then I can right-click the Users folder, select New User from the action menu, and fill out the following form:

Image of form

This form is from my laptop running Windows 8.1, but the form has not changed very much in the last ten years. In fact, the type of users and groups that are created are similar to the users and groups that were created back in the Windows NT 4.0 days.

We get a little bit of help by using the ADSI type accelerator to create the user object—but this has remained unchanged since Windows PowerShell 1.0 came out. The following are the steps required to create a new user object:

  1. Make a connection via ADSI to the local computer user account database. To do this, I specify that I am using WinNT, and I specify the name of the local computer.
    Note  WinNT must be capitalized exactly in this manner: WinNT, or the script will fail.
  2. Call the Create method after the connection is made.
  3. Specify that I am creating a user, and provide the name of the user.
  4. Call the SetPassword method, and pass a password.
  5. Call SetInfo method to create the actual user object.
  6. Assign a description.
  7. Call SetInfo method again.

Here is the script:

$cn = [ADSI]"WinNT://edlt"

$user = $cn.Create("User","mred")

$user.SetPassword("P@ssword1")

$user.setinfo()

$user.description = "Test user"

$user.SetInfo()

In the first line of the script, I specify the name of my local computer. Here it is named edlt. I use the [ADSI] type accelerator to make the connection, I specify WinNT, and I store the connection in a variable I call $cn.

Next, I call the Create method from the object stored in $cn. This method does not appear when using Tab expansion, or even in the lookup from within the ISE. With the first parameter of the Create method, I specify the type of object to create. Here I am creating a user. Next, I specify the name of the user object. I store the returned user object in the $user variable. This is shown here:

$user = $cn.Create("User","mred")

Now I specify a password by using the SetPassword method. The password must meet any complexity requirements set forth in the local security policy. This line is shown here:

$user.SetPassword("P@ssword1")

It is now time to write the user object to the local account database. To do this, I use the SetInfo method as shown here:

$user.setinfo()

Now I decide to add a description for the user. To do this, I write a value in the Description property of the user object. I once again call SetInfo. This is shown here:

$user.description = "Test user"

$user.SetInfo()

If I run this without Admin rights, I get an “Access denied” error message, as shown here:

Image of error message

On the other hand, if I run the script with Admin rights, after a few seconds, the script returns control to the Windows PowerShell ISE, and no output appears. This is shown here:

Image of command output

All the rest of this is plain, old, everyday Windows PowerShell. Some considerations include:

  • Add variables so everything is not hard-coded.
  • Pick up the name of the local computer.
  • Check for Admin rights before I run the script.
  • Don’t have the password in plain text in the script.
  • Create local user accounts on remote computers.
  • Put it into a function/module to make it easier to use.

All of these issues have been dealt with in other Hey, Scripting Guy! Blog posts. But I wanted to take today to focus specifically on the ADSI stuff. Better yet, upgrade to Windows PowerShell 4.0 and use DSC and the local user accounts provider, or use my Local User Management Module.

That is all there is to using Windows PowerShell to create local user accounts.  Join me tomorrow when I will explore more Windows PowerShell greatness.

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


PowerTip: Use PowerShell to Create Hash Table to Hold Lowercase Letters

0
0

Summary: Use Windows PowerShell to create a hash table that holds all ASCII lowercase letters.

Hey, Scripting Guy! Question How can I use Windows PowerShell to create a hash table that holds all lowercase ASCII letters?

Hey, Scripting Guy! Answer Create an empty ordered dictionary, and then use the add method to add the numbers 97-122
           as keys and the [char] associated with the number as the value, for example:

$a = [ordered]@{}

97..122 | foreach-object {$a.Add($_,[char]$_)}

Use PowerShell to Create Local Groups

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about creating local groups.

Microsoft Scripting Guy, Ed Wilson, is here. Creating a local group works exactly the same way as creating a local user account (see Use PowerShell to Create Local User Accounts). The process involves the following steps:

  1. Create a connection to the local user account database by using the [ADSI] type accelerator and WinNT.
  2. Use the connection to call the Create method, and specify two values for the method call: Group in the first position and the name of the group in the second position.
  3. Call SetInfo to write the group back to the local account database.
  4. Specify a value for the description.
  5. Call Setinfo again to write the description to the group.

  Notes 

  • When creating a local group, you must open the Windows PowerShell console or the Windows PowerShell ISE with Admin rights
  • When using WinNT, it must be capitalized exactly like this: WinNT.

At this point, there are no Windows PowerShell cmdlets from Microsoft that make it easy to create a local user account or a local group. Although it is possible to use the Desired State Configuration (DSC ) provider and the local account provider, this requires Windows PowerShell 4.0. There are a couple of modules written, such as my Local Account Management module, which expose advanced functions to make this easier. Other than that, it is old-school ADSI to the rescue.

Create the connection to the local account database

The first thing I do is use the ADSI type accelerator and the WinNT provider to make a connection to the local account database on my computer. I store the returned object in a variable named $cn as shown here:

$cn = [ADSI]"WinNT://edlt"

Call the create method to create the group

When I have my connection to the local account database, I can call the Createmethod. This method does not show up via Tab expansion or Get-Member. But it is available, and it does work. When I call the Createmethod, I supply two values. The first is the keyword Group, and the second is the name of the group. In the following example, I call the group mygroup:

$group = $cn.Create("Group","mygroup")

Call SetInfo

Now I need to call the SetInfo method to write the object back to the local account database:

PS C:\> $group.setinfo

 

OverloadDefinitions                                                           

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

Once again, note that the SetInfo method does not appear via Tab expansion. When I call this method, I must include empty parenthesis ( () )at the end of the method call, or else the syntax appears. Here is the command I use:

$group.setinfo()

Add a description

Now I want to add a description to the group. This is optional, but I consider it a best practice from when I used to be a network administrator. I would often find groups and service accounts that were created with no description and no information as to why they were there or what they were used for. By adding a description, the group becomes self-documenting. When I see a group with a description of “test group” I can be pretty safe in deleting it. Even better is the description “safe to delete.” Here is the command:

$group.description = "Test group"

$group.SetInfo()

The complete script is shown here:

# CreateLocalGroup.ps1

 

$cn = [ADSI]"WinNT://edlt"

$group = $cn.Create("Group","mygroup")

$group.setinfo()

$group.description = "Test group"

$group.SetInfo()

That is all there is using Windows PowerShell to creating a local group. Obviously, I need to add members to the group, and that is what I will discuss tomorrow. I can also use standard Windows PowerShell techniques to test for things like if the group exists or to create multiple groups.

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 

PowerTip: Use PowerShell to Reverse Array

0
0

Summary: Easily use Windows PowerShell to reverse an array.

Hey, Scripting Guy! Question How can I use Windows PowerShell to reverse an array?

Hey, Scripting Guy! Answer Pipe the array to the Sort-Object cmdlet and use the –Descending switch, for example:

$a = 1,2,3,4,5

$b = $a | sort -Descending

Adding Local Users to Local Groups

0
0

Summary: Learn how to use Windows PowerShell to add local users to local groups.

Microsoft Scripting Guy, Ed Wilson, is here. Creating local user objects and local group objects is fine, but to be useful, I need to be able to add users to groups. When working with local accounts, I need to use the ADSI type accelerator to facilitate the way things work.

   Note  This is the third in a series of three posts. If you haven't read them already, you might benefit from reading the first two posts before you read this:

Today I add the users to the group.

Connect to the actual group

Adding a user to a group is a bit different than creating a local user or a local group. When I add a user to a group, I need to connect to the group itself. I still need to open the Windows PowerShell console or ISE with Admin rights, but this time the connection is a bit more complicated. I still use the [ADSI] type accelerator, I still use WinNT as my provider, and I still specify the name of the computer. But I also must specify the name of the group and provide a hint that I am connecting to a group. Here is the command:

$group = [ADSI]"WinNT://edlt/mygroup,group"

If the group does not exist, the connection will not fail. In fact, it will actually appear to succeed. The error message will only appear when I try to use the object that I stored in the $group variable. This is shown here:

Image of error message

When I have a connection to an existing group, I call the Add method to add my local user object. The Add method accepts what is called an ADsPath—that is the complete path to the user object, including the WinNT provider. Here is an example of the ADsPath to the mred user that I created the other day:

"WinNT://edlt/mred,user"

There are four parts: WinNT, the computer name, the user account name are required. The fourth part, user,is a hint that is not really required, but it makes things go a bit faster because it tells the provider the type of object I am looking for. Here is the complete Add command:

$group.Add("WinNT://edlt/mred,user")

That is it. Two lines for the complete script:

$group = [ADSI]"WinNT://edlt/mygroup,group"

$group.Add("WinNT://edlt/mred,user")

I open the group in the computer management console, and sure enough, the user is now a member of the group.

Image of menu

That is all there is to using Windows PowerShell to add local users to local groups. Join me tomorrow when I will talk about how to make Windows PowerShell Help always display examples.

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 

PowerTip: Change Color of PowerShell Errors

0
0

Summary: Learn how to change the color of Windows PowerShell errors.

Hey, Scripting Guy! Question How can I change the color of Windows PowerShell errors in the Windows PowerShell console?

Hey, Scripting Guy! Answer Use $host.PrivateData and supply a new value for ErrorForegroundColor, for example:

$Host.PrivateData.ErrorForegroundColor = 'white'

Viewing all 3333 articles
Browse latest View live




Latest Images