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

Create and Parse Performance Monitor Logs with PowerShell

$
0
0

Summary: Learn how to create and parse performance monitor logs by using Windows PowerShell.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am wondering if I can use Windows PowerShell to create a performance counter log that I can use in Perfmon?

 

—CS

 

Hey, Scripting Guy! AnswerHello CS,

Microsoft Scripting Guy Ed Wilson here. After I finished giving my two talks on Windows PowerShell at SQLSaturday in Wheeling, West Virginia, I had the chance to talk to Ken M. and Matt L. who are both from Pittsburgh, Pennsylvania. Actually, The Scripting Wife and I had a chance to meet Ken M. at the PowerShell Deep Dive in Las Vegas. One of the things we were talking about was starting a new Windows PowerShell users group in Pittsburgh. I hope it will happen. By the way, next weekend (August 6–7, 2011) I have a couple of articles that talk about what is involved in starting a Windows PowerShell users group. If you would like to start a Windows PowerShell users group, shoot me an email at scripter@microsoft.com.  

Note   Performance counter cmdlets in Windows PowerShell work on Windows 7 and later. If you need to work with performance counter logs in an operating system before Windows 7, use the Relog.exe utility. This library article talks about other command-line tools that you might find useful in working with performance counters.

 

CS, when running scripts that might generate a lot of data, it is important to look at potential memory consumption. For example, let’s use the commands from yesterday’s Hey, Scripting Guy! Blog post that select the performance counter paths to collect processor, memory, and disk information. After I have gathered all the counters, I submit them to the Get-Counter cmdlet to begin the collection of the data. The commands is shown here:

$a = (Get-Counter -ListSet "Processor Information").paths

$a += (Get-Counter -listSet "memory").paths

$a += (Get-Counter -listSet "LogicalDisk").paths

$a += (Get-Counter -listSet "PhysicalDisk").paths

Get-Counter -Counter $a

 

If I want to use Windows PowerShell to create a performance log, the best way to do this is to use the pipeline and stream the data directly to the file. Streaming the data directly to a file will reduce the memory consumption of the command. I can use the continuous switched parameter to cause the Get-Counter cmdlet to stream data continuously. Because of the manual intervention required, this switch is limited to ad hoc performance testing. One would never kick off a scheduled task with the continuous switch. The modified command is shown here:

$a = (Get-Counter -ListSet "Processor Information").paths

$a += (Get-Counter -listSet "memory").paths

$a += (Get-Counter -listSet "LogicalDisk").paths

$a += (Get-Counter -listSet "PhysicalDisk").paths

Get-Counter -Counter $a -Continuous | Export-Counter -Path c:\fso\myperflog.blg

If I run this command from the Windows PowerShell ISE, I can click the red button to halt execution of the command. If I run this from the Windows PowerShell console, I need to press Ctrl+C to halt execution. This is because I used the continuous switch. The Export-Counter cmdlet has a maxsize parameter (to limit the size of a performance log), but it does not work when the Get-Counter cmdlet is run in continuous mode.

After I have finished collecting my data, I can open the .blg file directly in PerfMon, as shown in the following figure.

Image of the .blg file opened in PerfMon

To explore the data from the performance log, I import the performance log into a variable, index into the variable, and pull out a single snapshot. Remember from yesterday, the counter set on my laptop contains more than 220 counter paths, so for each snapshot, I have more than 220 performance points to examine.

The data I want to work with is stored in a property called countersamples. This is where I will find the information I am interested in examining.

If I want to find all counters in the first sample (that contains 220 performance points) that have a cooked value that is greater than 3000, I use the command following this paragraph. On my laptop, quite a few counters return from the command because of the various memory counters at work (my laptop has total memory that is greater than 3000 bytes). Anyway, here is a command that will return all cooked values that are greater than 3000. In this command, the [0] is used to index into the array of performance counter information, and return the first record set of data. The ? is an alias for the Where-Object cmdlet. The $_ is used to refer to each performance counter as it streams across the pipeline. The curly brackets indicate a script block that is supplied to the Where-Object cmdlet:

$a[0].countersamples | ? {$_.cookedvalue -gt 3000 }

To use Windows PowerShell to troll through the data and return useful information, it is necessary to use a compound query. In other words, I want to find counters that contain a certain word and have a certain value. For example, suppose I notice I have an issue with disk utilization. I might use a command such as the one shown here to identify all counters that contain the word logicaldisk in the path and that have a cooked value that is greater than 2. The reason for this query is that a diskqueue length greater than 2 could be signs of a problem. Here is the command (when using the –AND operator in a Where-Object cmdlet, it is necessary to repeat the $_ for each property with which you wish to work):

$a[0].countersamples | ? {$_.path -match 'logicaldisk' -AND $_.cookedvalue -gt 2 }

 

The output, as shown here, is not too enlightening because of the large number of counters that relate to disk space:

Path                                                                              InstanceName                CookedValue

\\edwils1\logicaldisk(harddiskvolume1)\% free space        harddiskvolume1            94.3312101910828

\\edwils1\logicaldisk(_total)\% free space                         _total                            38.6717598265006

\\edwils1\logicaldisk(c:)\% free space                               c:                                  38.0932520373644

\\edwils1\logicaldisk(harddiskvolume1)\free megabytes     harddiskvolume1            1481

\\edwils1\logicaldisk(_total)\free megabytes                     _total                            59022

\\edwils1\logicaldisk(c:)\free megabytes                           c:                                  57541

 

I can now begin to filter out the information, because I have a good idea of the type of data with which I am working. I also know that many of the returned counters are returning percentages of free space, and that is a value I am not too interested in right now. Because I am working with a collection of 195 different readings, one might be tempted to use the ForEach-Object cmdlet to work through the countersamples. After all, I did have to index into the array to begin working with the countersamples. The following command would work. It would also return only counters that match logicaldisks, but do not have the word free in the path:

$a | % {$_.countersamples | ? {$_.path -match 'logicaldisk' -AND $_.path -notmatch "free" -And $_.cookedvalue -gt 2 } }

A better approach, however, is to use the expandproperty property from the Select-Object cmdlet. This avoids the performance hit of interrupting the stream across the pipeline. To make the output easier to read, I pipe the output to the Format-Table cmdlet (this is one logical command that has broken across multiple lines; I have not included any line-continuation characters):

$a | select -ExpandProperty countersamples | ? {$_.path -match 'logicaldisk' -AND $_.path -n

otmatch "free" -And $_.cookedvalue -gt 2 } | ft cookedvalue, instancename, path –auto

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

 Image of command and associated output

But, I had said, I thought I had a problem with the disk queue. I change my query around a bit. For one thing, I do not need to exclude any counters with the word free in the path because I am looking for items to do with the disk queue. I am specifically interested in any counter dealing with a disk queue that has a length greater than 2. I pipe the results to the Format-Table cmdlet to make the display a bit easier to read. Here is the command (keep in mind it is a single-line command, and I have not added any line-continuation characters; therefore, if you run this command, make sure it is a single line):

$a | select -ExpandProperty countersamples | ? {$_.path -like '*logicaldisk*queue*' -And $_.

cookedvalue -gt 2 } | ft instancename, cookedvalue, path -auto

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

Image of command and associated output

Scrolling through the list, it is not obvious which problem I might actually have. I decide to sort the list. First, I sort by the queue length. I want the largest numbers on top, so I use the descending switched parameter. Once again, this is a single command:

$a | select -ExpandProperty countersamples | ? {$_.path -like '*logicaldisk*queue*' -AND $_.

path -notmatch "free" -And $_.cookedvalue -gt 2 } | sort cookedvalue -desc| ft instancename, cookedv

alue, path –auto

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

Image of command and associated output

I think I would like to see the counters sorted by path name. This will give me a feel for the ranges involved. The command is shown here (this is a single-line command; I have not included the backtick line continuation character):

$a | select -ExpandProperty countersamples | ? {$_.path -like '*logicaldisk*queue*' -AND $_.

path -notmatch "free" -And $_.cookedvalue -gt 2 } | sort path | ft instancename, cookedvalue, path -

auto

The command and associated output are shown here.

Image of command and associated output

Well, that is all there is to working with performance logs in Windows PowerShell.

 

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 ScriptCop to Help Write Better PowerShell Scripts

$
0
0

Summary: Learn about using the free tool called ScriptCop to help write better Windows PowerShell scripts.

 

Microsoft Scripting Guy Ed Wilson here. This weekend we have a two-part series from James Brundage. Take it away, James!

Getting started with ScriptCop

One of the problems with a new and rapidly changing technology such as Windows PowerShell is that there’s very little common practice. Everyone writes code their own way, and solves similar problems in very different ways with very different side effects. Implementations are inconsistent. 

There are many samples out there of good code, but very few checklists of what makes good code. Everyone starts out with the best intentions, but there has been nothing to help scripters do the “right” thing.

Developers tend to be slightly more irked by inconsistency than scripters, and so many languages have tools to help make sure people “do the right thing.” These tools look at information that you can find out about the source code or the DLL, but they do not run it. They just peek, analyze, and report back about what could be better. These tools are what are called “static analysis” tools, and they are a great thing to have in the toolkit.

ScriptCop is a static analysis tool for Windows PowerShell, to make sure your scripts are following the rules. ScriptCop has a number of built-in rules to help make your scripts better and to help avoid bugs. It’s also a framework for writing new rules, or writing “fixers” to help rewrite your code to do the right thing. You can run ScriptCop online, or download it from either http://scriptcop.start-automating.com or http://www.test-powershell.com/.

The main command you use in ScriptCop is Test-Command. You can either use Test-Command to tell you all of the issues in a single command, or you can have Test-Command tell you all of the issues in a module.

To get an idea of how it works, let’s go ahead and copy and paste a very simple function into ScriptCop online: http://scriptcop.start-automating.com/Test-Command.aspx. You can copy and paste one or more Windows PowerShell functions and test them in ScriptCop online. And you can test script files or modules you write by downloading ScriptCop and running it on your machine.

The function we’ll use will just be a simple “Hello, World”:

function HelloWorld

{

    "Hello, World"

}

I paste the function into the ScriptBlock box in the online version of ScriptCop, as shown in the following figure.

Image of pasting function into ScriptBlock box

After I test the function by clicking Test-Command, I get a simple to-do list to make the function better:

Rule

Problem

ItemWithProblem

Test-CommandNamingConvention

HelloWorld does not follow the verb-noun naming convention. PowerShell commands should be named like: StandardVerb-CustomNoun To see all of the standard verbs, run 'Get-Verb'

HelloWorld

Test-Help

HelloWorld does not have a help topic

HelloWorld

Test-DocumentationQuality

Code is sparsely documented (Only 0 % comments).

HelloWorld

Test-DocumentationQuality

HelloWorld does not define any #regions or #endregion

HelloWorld

 

As you can see, each item clearly indicates which command (or module) had the problem, what the problem was, and which rule flagged the issue.

To see all of rules, run:

Get-ScriptCopRule

Each rule goes through a batch of information about the command. Some rules look at the help content the command generates; others look at the code that make up the script. Many rules simply look at basic parameter information to check that parameters are the right types. Because you can use ScriptCop online, you can always be sure you’re using the most up-to-date set of rules.

Let’s fix some of the stuff ScriptCop recommends. Here’s a function with some fixes:

function Show-HelloWorldOnComputerName

{   

    <#

    .Synopsis

        Sample Show-HelloWorld on remote machines

    .Description

        Actually, this does nothing; it's there to demonstrate good scripting technique.

    .Example

        Show-HelloWorldOnComputerName

    #>

   

    param(

    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

    [string]

    $ComputerName,

   

    $Credential

    )

   

    #region Parameter Block

    "hello world"

    #endregion Parameter Block

}

When we run it thru the wringer again, it gives us:

Test-ForCommonParameterMistake

Show-HelloWorldOnComputerName Credential parameter be a [Management.Automation.PSCredential] Parameter

Show-HelloWorldOnComputerName

Test-Help

Not all parameters in Show-HelloWorldOnComputerName have help. Parameters without help: ComputerName Credential

Show-HelloWorldOnComputerName

Test-ProcessBlockImplemented

Show-HelloWorldOnComputerName can take values from the pipeline, but has no process block

Show-HelloWorldOnComputerName

 

Test-ForCommonParameterMistake lets us know that credentials need to be of a certain type. Test-Help no longer complains that there is not a topic, but does complain about the lack of linked topics and about the lack of help on the ComputerName and CredentialParameter. Test-ProcessBlockImplemented points out a serious bug: there’s no process block (which means only the last item piped in will run).

With a few more slight changes, we’re clean:

function Show-HelloWorldOnComputerName

{   

    <#

    .Synopsis

        Sample Show-HelloWorld on remote machines

    .Description

        Actually, this does nothing, it's there to demonstrate good scripting technique

    .Example

        Show-HelloWorldOnComputerName

    .Link

        Invoke-Command

    #>

   

    param(

    # The remote computer name

    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]

    [string]

    $ComputerName,

   

    # the credential to connect

    [Management.Automation.PSCredential]

    $Credential

    )

   

    process {

        #region Parameter Block

        "hello world"

        #endregion Parameter Block

    }

}

ScriptCop gives you a simple, easy-to-use checklist of what’s left to fix with your scripts before they go out the door. Having static analysis tools such as this in your script kit can make it simple to perfectly and consistently polish every script on the way out the door.

Tune in tomorrow so that you can learn how to write your own ScriptCop rules, and how to run ScriptCop on a module.

 

Thanks, James!

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

 

 

Create Your Own PowerShell Rules for ScriptCop

$
0
0

Summary: James Brundage teaches how to create your own Windows PowerShell rules for ScriptCop.

 

Microsoft Scripting Guy Ed Wilson here. Today, we have part two (including the conclusion) to the article begun by James Brundage yesterday. Take it away, James!

 

Writing your own ScriptCop rules

Yesterday, I introduced you to ScriptCop, the tool to help make sure your scripts are following the rules.  Today, I’ll help you fine-tune the rules ScriptCop follows. 

Every organization does things its own way. Some people like tabs. Some like spaces. Some like aliases. Others like fully qualified commands. Some domains won’t let an unsigned script within 50 feet, and some lay out the welcome mat of “Bypass.” ScriptCop lets you write simple rules to enforce your organization’s best practices. 

To do this with ScriptCop, you’ll need to download it instead of running it online. You can download it from the Start-Automating ScriptCop site. After you unblock the file, you can unzip it to Documents\WindowsPowerShell\Modules. The scripts themselves are not signed. 

ScriptCop rules live in the rules directory. ScriptCop rules are special functions or scripts that follow one rule of their own: Test-ScriptCopRule. This makes sure the rule has the right parameters so that it can get summarized information about your command without running your command. Remember, these are “static” analysis tools: we look, but don’t touch; therefore your script is not modified or harmed in any way. 

There are a few different things we analyze, and you can write a rule that uses any of them:

  • The CommandMetaData of the command.
  • The PSModuleInfo of the module.
  • The list of PSScriptTokens and text in each command.
  • The MamlCommandHelpInfo for a command. 

The items in bold are typenames. Use them or the good old Get-Member cmdlet to find out which information is available as you write each rule. 

In today’s blog post, I will help you to write four very quick rules:

  • A rule to make sure that any parameter named name is a string.
  • A rule to make sure the module is at least version 1.0.
  • A rule to check that the command doesn’t use the Write-Host cmdlet.
  • A rule to check that the command has author information. 

A ScriptCop rule is really simplicity itself. It looks at a piece of information and writes out an error. 

The first rule, to make sure that any parameter named name is a string, is simple: We have a copy/paste header in the rule that sets up the parameters. 

The $CommandInfo variable will have the metadata about the command, including a dictionary for each parameter. We look in that dictionary for name, and then check if its parameter type isn’t a string. If it’s not, we just use the Write-Error cmdlet. When you use the Write-Error cmdlet within a ScriptCop rule, it marks the rule as having failed. The resulting function is shown here.

function Test-ParametersNamedNameAreStrings

{

    param(

    [Parameter(ParameterSetName='TestCommandInfo',Mandatory=$true,ValueFromPipeline=$true)]

    [Management.Automation.CommandInfo]

    $CommandInfo

    )

                  

    process {       

        if ($commandInfo.parameters['Name'] -and

            $commandInfo.parameters['Name'].ParameterType -ne [string]) {

            Write-Error -Message "$commandInfo -Name is not a string"           

        }

    }

}

 

The second rule, the one to determine if the module is at least version 1.0, is just as easy to create. The key here is knowing that the $moduleInfo variable has a property named version. Other than that, it is pretty straightforward. Here is the complete function: 

function Test-ModuleVersionIsAtLeastOne

{

    param(

    [Parameter(ParameterSetName='TestModuleInfo',Mandatory=$true,ValueFromPipeline=$true)]

    [Management.Automation.PSModuleInfo]

    $ModuleInfo

    )

   

    process {

        if ($ModuleInfo.Version -lt '1.0' ) {

            Write-Error "$ModuleInfo Version must be 1.0 or greater.  It was $($moduleInfo.Version)."

        }

    }

}

 

The check for the Write-Host cmdlet is similarly straightforward. Unfortunately, the parameters that this example uses are a little longer. The style of the parameters for a ScriptCop rule is what let’s it work. The styles are very picky, so always simply copy and paste the parameters from another ScriptCop rule. To learn more or see all of the possible signatures, run: 

Get-Help about_scriptcop_rules

 

Here’s the Write-Host. Don’t be daunted by all the parameter code; it was copied and pasted. The meat of the rule is just these few lines: 

$hasWriteHost = $ScriptToken |

            Where-Object { $_.Type -eq "Command" -and $_.Content -eq "Write-Host" }

       

        if ($hasWriteHost) {

            Write-Error "$ScriptTokenCommand uses Write-Host.  Write-Host makes your scripts unsuable inside other scripts."

            return

        }

 

Here’s the meat and potatoes: 

Test-DoesNotUseWriteHost

{

    #region     ScriptTokenValidation Parameter Statement

    param(

    <#   

    This parameter will contain the tokens in the script, and will be automatically

    provided when this command is run within ScriptCop.

   

    This parameter should not be used directly, except for testing purposes.       

    #>

    [Parameter(ParameterSetName='TestScriptToken',

        Mandatory=$true,

        ValueFromPipelineByPropertyName=$true)]

    [Management.Automation.PSToken[]]

    $ScriptToken,

   

    <#  

    This parameter will contain the command that was tokenized, and will be automatically

    provided when this command is run within ScriptCop.

   

    This parameter should not be used directly, except for testing purposes.

    #>

    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]

    [Management.Automation.CommandInfo]

    $ScriptTokenCommand,

   

    <#

    This parameter contains the raw text of the script, and will be automatically

    provided when this command is run within ScriptCop

   

    This parameter should not be used directly, except for testing purposes.   

    #>

    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]

    [string]

    $ScriptText

    )

    #endregion  ScriptTokenValidation Parameter Statement

   

   

    process {             

       $hasWriteHost = $ScriptToken |

            Where-Object { $_.Type -eq "Command" -and $_.Content -eq "Write-Host" }

       

        if ($hasWriteHost) {

            Write-Error "$ScriptTokenCommand uses Write-Host.  Write-Host makes your scripts unsuable inside other scripts."

            return

        }

    }

}

 

The last rule is the most complex, and by that, I mean it’s not really that hard at all. This one line looks at $helpContent (which comes preloaded with all of the Help) and tells us if it has an author: 

$hasAuthorInNotes = $helpContent.alertSet | Out-string -Width 1024 | Where-Object { $_ -ilike "author:*" } 

 

function Test-AuthorInNotes

{

    param(

    [Parameter(ParameterSetName='TestHelpContent',ValueFromPipelineByPropertyName=$true)]  

    $HelpContent,

   

    [Parameter(ParameterSetName='TestHelpContent',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]

    [Management.Automation.CommandInfo]

    $HelpCommand

    )

   

    process {

        if (-not $HelpContent) {           

            return

        }

       

        $hasAuthorInNotes = $helpContent.alertSet | Out-string -Width 1024 | Where-Object { $_ -ilike "author:*" }

       

        if (-not $hasAuthorInNotes) {

            Write-Error "$HelpCommand does not include an author in the help notes."

        }

    }

}

 

To use each of these rules, simply create a new file for each rule in the Rules folder inside the ScriptCop module directory. Then, reload ScriptCop by typing Import-Module ScriptCop –Force.

 

To run ScriptCop locally on or more commands, use: 

Get-Command $NameOfCommand  | Test-Command 

To run ScriptCop on or more modules, use: 

Get-Module $NameOfModule | Test-Command

 To run just one or two rules (like your new rules), run: 

Test-Command –Rule $NameOfRule           

To exclude rules, use: 

Test-Command -ExcludedRule $NameOfRule

By using the rules ScriptCop gives you and writing your own, you can ensure that every script in your company’s repository is exactly the way you want it to be. To find out more about the types of rules you can create, type Get-Help about_scriptcop_rules.  To download or use the latest version of the tool go, to http://scriptcop.start-automating.com/

ScriptCop is a powerful tool to help your scripts follow the rules. Now you have the power to pick and choose which rules to follow. With great power comes great responsibility—use it wisely. 

 

James, thank you for creating ScriptCop, and for taking the time to share with us some examples for its use. 

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 the PowerShell Grep Command to Parse the Command Line

$
0
0

Summary: In this article, Microsoft Scripting Guy Ed Wilson teaches how to use the Windows PowerShell version of grep to parse the command line.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have enjoyed reading Sean’s legacy scripting articles, but I am a bit confused. It seems that all these commands return data, and I would love to be able to easily parse the information that the commands return. Can you give me an example of that?

—KG

 

Hey, Scripting Guy! AnswerHello KG,

Microsoft Scripting Guy Ed Wilson here. The TechReady conference in Seattle last week was great. We even had a few sun sightings during the week. The day I flew into Seattle was absolutely beautiful, and I was able to see all the mountains stretching up into the sky. The Space Needle was clearly visible. All in all, it was a Seattle Chamber of Commerce type of day.

KG, if you are going to work with legacy commands very often, you will need to become competent at using the Select-String cmdlet. Often, I will look around for an object that will return the same types of information that I might obtain via a legacy command—to avoid the issue of extensive text parsing.

For example, the ipconfig command will return an IP address and a log of other information. But I can use WMI to obtain the IP address as well. The following command returns the IP address on my local computer. Note that gwmi is an alias for the Get-WMIObject cmdlet. I only have one network interface card that is ipenabled, so I can return the ipaddress property directly. Unfortunately, the ipaddress property returns an array with both the IPv4 and IPv6 address in it. I index into the array to return only the IPv4 address:

(gwmi win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]

As you can tell, typing the preceding command is a bit cumbersome when compared to typing ipconfig. However, if I put the above command in a function and I place it in my profile or some module, I can call it as easily as typing Get-IPAddress. Or if I create an alias, I can type gia. The Get-IPAddress function and code to create an alias are shown here:

Get-IPAddressFunction.ps1

Function Get-IPAddress

{

 (Get-WmiObject -class win32_NetworkAdapterConfiguration `

   -Filter 'ipenabled = "true"').ipaddress[0]

} #end function Get-Ipaddress

 

New-Alias -Name GIA -Value Get-IPAddress -Description "HSG alias"

 

When I run the function to load it into memory, I can then access it by alias if I wish. This is shown in the following figure.

Image of accessing function by alias

Figuring out how to use the Select-String cmdlet can be a bit confusing. The online help for the cmdlet is rather extensive, and the examples do not seem to cover simple cases. For example, if I want to parse the results of the ipconfig command, I might be tempted to do something like this:

PS C:\> Select-String -Pattern "IPV4" -InputObject ipconfig

PS C:\>

Unfortunately, nothing is returned. I try putting parentheses around the command to force evaluation first, and I at least see something returned to the screen. The command is shown here:

Select-String -Pattern "IPV4" -InputObject (ipconfig)

The command and output are shown in the following figure.

Image of command and associated output

As can be seen in the preceding figure, the results are completely jumbled and unreadable. It seems that it might be a lost cause, but if you stick with it and use the pipeline, the output is much more readable. The command that uses the pipeline and the associated output are shown here (in addition, it does not matter if I use the simplematch switched parameter or not):

PS C:\> ipconfig | Select-String -Pattern "IPv4" -SimpleMatch

   IPv4 Address. . . . . . . . . . . : 10.0.0.188

 

PS C:\> ipconfig | Select-String -Pattern "IPv4"

   IPv4 Address. . . . . . . . . . . : 10.0.0.188

 

When I try to shorten the command, however, problems arise. I try to use select as an alias for the Select-String cmdlet, and no errors appear, but I am left with a blank screen, as shown in the following figure.

Image of blank screen

The problem is that select is an alias for the Select-Object cmdlet, and not for the Select-String cmdlet, as shown here:

PS C:\> Get-Alias select

 

CommandType  Name               Definition

Alias                  select                Select-Object

 

Well, what are the aliases for the Select-String cmdlet? According to the output, there isn’t even one:

PS C:\> Get-Alias -Definition select-string

Get-Alias : This command cannot find a matching alias because an alias with definition 'select-string'

 does not exist.

At line:1 char:10

+ Get-Alias <<<<  -Definition select-string

    + CategoryInfo          : ObjectNotFound: (select-string:String) [Get-Alias], ItemNotFoundException

    + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

Now, I may decide that I would like to create an alias for Select-String. Grep would be a good one to use. The command to accomplish this appears here (you would want to place it in your startup profile; see this collection of Hey, Scripting Guy! posts for information about profiles):

New-Alias -Name grep -Value Select-String -Description "HSG alias"

When I have an alias for Select-String, I can shorten my command quite a bit. The shortened command and associated output are shown here:

PS C:\> ipconfig | grep "IPv4"

   IPv4 Address. . . . . . . . . . . : 10.0.0.188

 

KG, this should be enough to get you started playing around with the Select-String cmdlet. Join me again tomorrow, when we will explore some more ways to use this powerful cmdlet.

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 the PowerShell Select-String Cmdlet to Parse WMI Output

$
0
0

Summary: Learn how to use the Windows PowerShell Select-String cmdlet to easily parse WMI output.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have a quick question: is it possible to parse the output of some of the WMI commands? I know they return objects, but some of the commands return sooooo much data. I would like a quick and easy way to look for specific things such as errors or failures, without a lot of fuss and muss. Is this possible?

—DD

 

Hey, Scripting Guy! AnswerHello DD,

Microsoft Scripting Guy Ed Wilson here. And, yes, it is possible. When working with WMI classes, I normally use the Where-Object cmdlet to perform a filter, or I will use WMI itself to perform the filter. In general, if I am working interactively from the Windows PowerShell prompt, my first choice is to use the Where-Object cmdlet (the alias is ?) simply because it is more natural to me and is easier to type. But it is not as fast. For example, if I am working with the Win32_ReliabilityRecords WMI class, it can return a lot of data. If I am only looking for status messages that contain the word failed in them, I might pipe the results to the Where-Object cmdlet. The command is shown here (gwmi is the alias for the Get-WmiObject cmdlet, and ? is the alias for the Where-Object cmdlet):

gwmi win32_reliabilityrecords | ? { $_.message -match "failed" }

The command and the associated output are shown here.

Image of command and associated output

As you can see in the preceding figure, a lot of information is returned. These are all objects, so I have the opportunity to do additional processing, if I want to do so. However, what if I do not want to do additional processing? In fact, what if all I want to do is see messages that contained the word failed in them? In this case, I might turn to the Select-String cmdlet. The syntax of such a command is shown following this paragraph. This command uses the gwmi alias for the Get-WmiObject cmdlet. There is no default alias for the Select-String cmdlet. In yesterday’s Hey, Scripting Guy! Blog post, I showed how to create an alias called grep to make it easier to use the Select-String cmdlet from an interactive Windows PowerShell prompt.

gwmi win32_reliabilityrecords | select-string "failed" -InputObject {$_.message}

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

Image of command and associated output

As shown in the preceding figure, the output is much more succinct and easier to read. So how does the command work? I use the Get-WmiObject cmdlet to query for all records from the Reliability Provider. The Reliability Provider supports the Win32_ReliabilityRecords WMI class. I pipe all of these records to the Select-String cmdlet. The Select-String cmdlet uses the pattern “failed” and searches each incoming message property. Here is the trick: I must use curly brackets along with the $_ automatic variable to read inside the message property. Normally I would use simple parentheses to force the evaluation, but that does not work here. Leaving the inputobject value unadorned does not work either.

What about speed? I decided to use the Measure-Command cmdlet to measure the performance of the two commands. The two commands are shown here:

measure-command {gwmi win32_reliabilityrecords | ? { $_.message -match "failed" } }

measure-command {gwmi win32_reliabilityrecords | select-string "failed" -InputObject {$_.message} }

As seen in the following figure, the first command (using the Where-Object cmdlet) takes about 10.1 seconds. The second command (using the Select-String cmdlet) takes about 10.5 seconds. So the first command is a bit faster.

Image of speed of each command

Unfortunately, the WMI Reliability Provider does not appear to support the WMI like operator. Therefore, the following command fails.

PS C:\> gwmi win32_reliabilityrecords -filter "message -like '%failed%'"

Get-WmiObject : Invalid query

At line:1 char:5

+ gwmi <<<<  win32_reliabilityrecords -filter "message -like '%failed%'"

    + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], ManagementException

    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObject Command

By selecting only the message property, the query speeds a little. The output is better formatted as well:

PS C:\> gwmi win32_reliabilityrecords -Property message | ? {$_.message -match "failed" } 

 

__GENUS          : 2

__CLASS          : Win32_ReliabilityRecords

__SUPERCLASS     :

__DYNASTY        :

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

Message          : Installation Failure: Windows failed to install the following update with error

                   0x80070643: Definition Update for Windows Defender - KB915597 (Definition 1.99.1

                   460.0).

 

__GENUS          : 2

__CLASS          : Win32_ReliabilityRecords

__SUPERCLASS     :

__DYNASTY        :

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

Message          : Installation Failure: Windows failed to install the following update with error

                   0x80070643: Definition Update for Microsoft Forefront code named Stirling Beta v

                   ersion - KB977939 (Definition 1.95.257.0).

 

The time of the previous query is shown here:

PS C:\> Measure-command {gwmi win32_reliabilityrecords -Property message | ? {$_.message -match "failed"} } 

 

Days              : 0

Hours             : 0

Minutes           : 0

Seconds           : 8

Milliseconds      : 911

Ticks             : 89110704

TotalDays         : 0.000103137388888889

TotalHours        : 0.00247529733333333

TotalMinutes      : 0.14851784

TotalSeconds      : 8.9110704

TotalMilliseconds : 8911.0704

 

If I want to return only the message property and not the WMI System properties, I can pipe the results to the Select-Object cmdlet and choose only the message property. This command and associated output are shown here:

PS C:\> gwmi win32_reliabilityrecords -Property message | ? {$_.message -match "failed" } | select message 

message

-------

Installation Failure: Windows failed to install the following update with error 0x80070643: Defi...

Installation Failure: Windows failed to install the following update with error 0x80070643: Defi...

 

As can be seen above, however, the message field is truncated. Therefore, to display the entire message property, it is necessary to use the expandproperty parameter. This is shown here:

PS C:\> gwmi win32_reliabilityrecords -Property message | ? {$_.message -match "failed" } | select -

ExpandProperty message

Installation Failure: Windows failed to install the following update with error 0x80070643: Definit

ion Update for Windows Defender - KB915597 (Definition 1.99.1460.0).

Installation Failure: Windows failed to install the following update with error 0x80070643: Definit

ion Update for Microsoft Forefront code named Stirling Beta version - KB977939 (Definition 1.95.257

.0).

At this point, it is a bit faster and certainly easier to go back to using the Select-String cmdlet. It might make sense to modify it to return only the message property. This revised command and associated output are shown here:

PS C:\> gwmi win32_reliabilityrecords -property message | select-string "failed" -InputObject {$_.me

ssage}

 

Installation Failure: Windows failed to install the following update with error 0x80070643: Definit

ion Update for Windows Defender - KB915597 (Definition 1.99.1460.0).

Installation Failure: Windows failed to install the following update with error 0x80070643: Definit

ion Update for Microsoft Forefront code named Stirling Beta version - KB977939 (Definition 1.95.257

.0).

 

PS C:\>

  

Well, DD, that is about all there is to using the Select-String cmdlet to parse WMI data. Join me tomorrow when we will continue our discussion about parsing output.

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

 

 

Learn How to Use PowerShell to Parse the Firewall Log

$
0
0

Summary: Microsoft Scripting Guy Ed Wilson shows how to use Windows PowerShell to parse the Windows Firewall log.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am wondering about the firewall log on my computer. I was in a hotel recently, and I noticed that the network adapter light kept flashing, but I was not like doing anything really. When I tried to look at the firewall log, it seems there was nothing there. Did they get rid of the firewall log in Windows 7? If they did not, where is it? Oh, yeah, if there is a firewall log, can you show me how to use Windows PowerShell to search it for cool stuff, so I can maybe figure out why the network is going crazy, when I am not doing anything?

—BP

 

Hey, Scripting Guy! AnswerHello BP,

Microsoft Scripting Guy Ed Wilson here. Having recently returned from a road trip that entailed nearly two weeks in hotels, I can sympathize with you about connecting to strange networks. I can assure you that the firewall in Windows 7 does in fact possess the ability to create a log. Whether or not that log is enabled is a matter of personal preference, Group Policy preference, or default behavior.

First, the defaults: on both Windows 7 and Windows Server 2008 R2, Windows Firewall is enabled, and the logs are created in the %systemroot%\system32\logfiles\firewall directory. The file is called pfirewall.log. Unfortunately, the default logging behavior is to not log dropped packets or successful connections, which means there is no logging actually going on. I recommend changing this to log both dropped packets and successful connections.

Second, Windows Firewall logging can be controlled via Group Policy. In this case, you would not be able to change any of the logging settings. Via Group Policy, the logging level and the log storage location are configurable. The following figure shows the firewall log details on a computer that has settings managed via Group Policy. In the figure, you can see that both the location and the log name have changed from the defaults.

Image of firewall log details on computer managed via Group Policy

In addition, because the Windows Firewall can be set with different settings based on the detected network connection type, you could have different logging taking place from the Domain profile, the Private profile, and the Public profile. I would not necessarily recommend this configuration, but it certainly could be done. From a management standpoint, I think it would be easier to simply log both successful connections and dropped packets to the same log from all three firewall profiles—in this way, only one log file must be parsed. I also recommend keeping both the default location and the default name of the firewall log as well. If default log names and locations are maintained, it makes it easy for scripts to obtain the information. The NetSh utility can be used to obtain logging locations for the three different profiles. The actual NetSh command is shown here:

netsh advfirewall show allprofiles

The command and output are shown in the following figure.

Image of command and associated output

I can use the Select-String cmdlet to parse that output and return the firewall log locations. This command and associated output are shown here:

PS C:\> netsh advfirewall show allprofiles | Select-String Filename

 

FileName                              %systemroot%\system32\LogFiles\Firewall\pfirewall.log

FileName                              %systemroot%\system32\LogFiles\Firewall\pfirewall.log

FileName                              %systemroot%\system32\LogFiles\Firewall\pfirewall.log

 

From examining the above output, I am able to see that all logging takes place in the same log. I am not able to use %systemRoot% directly from within Windows PowerShell. I can, however, use the systemroot environmental variable, as shown in the following code:

PS C:\> $env:SystemRoot

C:\Windows

I can even use the –replace operator, and replace %systemroot% if I wish to do so, as shown here (the % symbol is an alias for the ForEach-Object cmdlet):

PS C:\> netsh advfirewall show allprofiles | Select-String Filename | % { $_ -replace "%systemroot%"

,$env:systemroot }

FileName                              C:\Windows\system32\LogFiles\Firewall\pfirewall.log

FileName                              C:\Windows\system32\LogFiles\Firewall\pfirewall.log

FileName                              C:\Windows\system32\LogFiles\Firewall\pfirewall.log

 

BP, the one thing to keep in mind when working with any log that is stored in the windows directory is that it will require administrator rights for access. In Windows PowerShell, this means right-clicking the Windows PowerShell icon, and selecting Run as Administrator from the shortcut menu.

Using the Select-String cmdlet, I can read and parse the log in one operation. This command appears here. For readability, I am going to store the path in the firewall log in a variable. This variable assignment is shown here:

$fwlog = "C:\Windows\system32\LogFiles\Firewall\pfirewall.log"

Select-String -Path $fwlog -Pattern "drop"

To allow me to read the log, I am going to pipe the output to more. This command is shown here:

Image of command and associated output

Though it is permissible to use the Select-String cmdlet to read and to parse a log at the same time, if I am trying to analyze the log, it is not very efficient. This lack of efficiency is because every time I want to look for something else, I need to read the contents of the log again. Therefore, if I am going to be spending very much time attempting to parse the log and look for different things, I will store the contents of the log into a variable. My commands to store the path to the log in a variable, get the contents of the log, store them in a variable, and finally parse the log contents for the word drop are shown here: 

$fwlog = "C:\Windows\system32\LogFiles\Firewall\pfirewall.log"

$logcontents = Get-Content $fwlog

$logcontents | select-string "drop"

 

If I want to see how many dropped UDP and TCP packets appear in the log, I can obtain that information by using the Measure-Object cmdlet:

PS C:\> $logcontents | Select-String "drop udp" | Measure-Object

 

Count    : 33436

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

So, there are 33,436 dropped UDP packets. What is the timespan that is covered by this log? To figure this out, I use the Select-Object cmdlet (the alias is select). I choose the -first and the -last entries.

PS C:\> $logcontents | Select-String "drop udp" | select -First 1

2011-07-29 00:10:13 DROP UDP 10.0.1.185 10.0.127.255 137 137 78 - - - - - - - RECEIVE

PS C:\> $logcontents | Select-String "drop udp" | select -last 1

2011-07-29 01:20:50 DROP UDP 10.0.0.146 10.0.127.255 137 137 78 - - - - - - - RECEIVE

From looking at the preceding entries, it seems the log runs from a little after midnight for about an hour. Actually, we have 33,436 dropped UDP packets in 1 hour and 10 minutes. If I want to look at the number of dropped TCP packets, I perform the same operation. As shown here, there were only 43 dropped TCP packets:

PS C:\> $logcontents | Select-String "drop tcp" | Measure-Object

Count    : 43

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

I can easily modify the query to find out the numbers of permitted TCP and UDP packets, as shown here:

PS C:\> $logcontents | Select-String "allow udp" | measure-object

Count    : 4527

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

PS C:\> $logcontents | Select-String "allow tcp" | measure-object

Count    : 940

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

 

Other patterns can be used to parse the log file. They would follow standard patterns for either regular expressions or wildcard characters.

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 an Easy PowerShell Command to Search Files for Information

$
0
0

Summary: Learn how to use a Windows PowerShell command to search easily for information in a collection of files.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I need to be able to parse multiple files for text that are in a single folder. I hate to have to write a script for such a common task, but I am afraid I will have to do so. Can you help me?

—SH

 

Hey, Scripting Guy! AnswerHello SH,

Microsoft Scripting Guy Ed Wilson here. The Scripting Wife and I are trying to get things sorted out this week before we leave for Corpus Christi, Texas, where I will be teaching a Windows PowerShell class. In addition, we will be appearing at the inaugural meeting of the Corpus Christi PowerShell User Group meeting. If you will be in South Texas on August 9, 2011, you should come check it out. It should be great fun. Oh, by the way, I am doing a meeting today with Lincoln SQL Server User Group (ssug). The meeting will be available via Live Meeting. It feels like this week started late and will end early. Luckily, SH, the answer to your question is no, you do not have to write a script to parse a folder full of files for a particular string. In fact, it was a topic that was tested in the Beginner Event 6 in the 2011 Scripting Games.

The solution is to use the Select-String cmdlet. One thing to keep in mind is that the Select-String cmdlet reads text files; it cannot read the more complicated file types such as .doc and .docx files that are generated by Microsoft Word. When I attempted to search a folder containing the Word documents and pictures that make up a typical Hey, Scripting Guy! Blog post, Windows PowerShell displayed a bunch of gibberish in the console, and then locked up. This is shown in the following figure.

Image of gibberish output

The easy way to avoid producing gibberish is to specify the file types you want to search. For example, if I want to search all text files in the c:\fso directory for a pattern of ed (such as my first name), I include a wildcard character in my path specification, and choose any file that has the file extension of .txt. The nice thing about the Select-String cmdlet is that it expects the path as well as the pattern parameter to be strings, so I do not need to use quotation marks for either the pattern or the path. I can use the following command to search the c:\fso folder for files that have the .txt file extension, and contain a pattern match for ed:

Select-String -Path c:\fso\*.txt -pattern ed

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

Image of command and associated output

If I use the Get-Command cmdlet (gcm is an alias for this cmdlet) to examine the syntax for the Select-String cmdlet, I see that both the path and the pattern parameters will accept an array of strings. This means that I can use the wildcard character trick with the file extensions to look for multiple files at the same time. To examine only the syntax of the Select-String cmdlet, I used the Get-Command cmdlet and piped the output to the Select-Object cmdlet (select is an alias). I then chose to expand the definition property. The resulting command is shown here:

gcm select-string | select -expand definition

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

Image of command and associated output

Because I can supply an array of strings to the path parameter, I can search for both .log files and .txt files at the same time. In my revised Select-String command, I search the c:\fso folder for both .txt and .log files. I look inside both types of files for a pattern match of ed. The revised command is shown here:

Select-String -Path c:\fso\*.txt, c:\fso\*.log -pattern ed

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

Image of command and associated output

Because the pattern parameter also accepts an array of strings, I can also search the .txt and the .log files for both ed and teresa strings. The command to search the c:\fso folder for both .txt and for .log files, and to look for pattern matches with both ed and teresa is shown in the following figure.

Image of searching folder and pattern matching

In addition to directly using the path parameter in the Select-String cmdlet, it may be easier to use the Get-Childitem cmdlet for more granular control over the files to be parsed. In the following command, I use the dir command (an alias for the Get-ChildItem cmdlet) and provide the path of c:\fso (the path does not appear in the command because it is the default parameter). I include only .txt and .log files (I use the –I and rely on parameter completion to specify the include parameter. I do the same thing with the recurse switch (in that I just use the letter r). I pipe the results to the Select-String cmdlet and look for the pattern fail (pattern is the default parameter and therefore is omitted in the command). The long version of the command is shown here:

Get-ChildItem -Path c:\fso -Include *.txt, *.log -Recurse | Select-String -Pattern fail

Here is an example of the shorter form of the command.

dir c:\fso -I *.txt, *.log -R | Select-String fail

The command and associated output are shown here.

Image of command and associated output

Interestingly enough, the above output displays information from an install.log file, and it shows a bunch of failures. I decide that I would like to see the successes as well as the failures. I modify the command by adding the word success to the pattern. The revised command is shown here:

dir c:\fso -I *.txt, *.log -R | Select-String fail, success

Image of all successes being client workstations 

As I look over the output from the previous command, I see a pattern appearing: on all the servers, the installation failed. On the client computers, the installation was a success. But I am missing my Windows XP computers in the output. I decide to add the word pending to my array of search terms. Here is the revised command:

dir c:\fso -I *.txt, *.log -R | Select-String fail, success, pending

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

Image of command and associated output

 

Well, SH, thank you for your question. I hope I have encouraged you to spend a bit more time exploring the Select-String cmdlet.

I invite you to follow me on Twitter and to join the Scripting Guys on 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 Open Files That Contain Matching Words

$
0
0

Summary: Learn how to use Windows PowerShell to search a folder for matching strings and to open them automatically in Notepad.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have been enjoying using the Select-String cmdlet, but I have a question. When I use Select-String, it lists the file that it finds the match in, but I have to copy and paste from there in order to open it in Notepad. I know that opening a file in Notepad is not very “PowerShell-like” but I still need the ability to quickly find and modify things in Notepad. Can you help me automate this process a bit?

—BL

 

Hey, Scripting Guy! AnswerHello BL,

Microsoft Scripting Guy Ed Wilson here. One of the things I love about Windows PowerShell is the discoverability aspect of the language. For example, it is seemingly impossible to have a comprehensive document on Windows PowerShell. There is just so much that can be accomplished and are so many different ways of using Windows PowerShell that by the time a complete reference guide was produced, several new versions of Windows PowerShell would have been released. Besides, if a complete reference guide were ever written, no one would have time to read it anyway.

To counterbalance this seeming deficit, Windows PowerShell has very good discoverability. One of the primary tools for anyone who wants to get beyond simply running cmdlets with documented switches and parameters is the Get-Member cmdlet.

Note   This is the fifth article in a series of five articles that talk about using the Select-String cmdlet. On Monday, I talked about using the Select-String cmdlet to parse the data returned from an ipconfig command to return only the IP address. On Tuesday, I discussed using the Select-String cmdlet to parse data returned from WMI. Wednesday found me talking about parsing the firewall log, and yesterday I talked about parsing a collection of files in a folder to look for pattern matches in files.

For example, if I pipe the results from a Select-String command, I can see that the command returns a MatchInfo object. This command and output are shown here:

PS C:\fso> dir c:\fso -I *.txt, *.log -R | Select-String fail | gm

 

   TypeName: Microsoft.PowerShell.Commands.MatchInfo

 

Name               MemberType                 Definition

Equals               Method                         bool Equals(System.Object obj)

GetHashCode    Method                         int GetHashCode()

GetType            Method                         type GetType()

RelativePath       Method                         string RelativePath(string directory)

ToString            Method                         string ToString(), string ToString(string directory)

Context             Property                        Microsoft.PowerShell.Commands.MatchInfoContext Conte...

Filename           Property                        System.String Filename {get;}

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

Line                  Property                        System.String Line {get;set;}

LineNumber      Property                        System.Int32 LineNumber {get;set;}

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

Path                  Property                        System.String Path {get;set;}

Pattern              Property                        System.String Pattern {get;set;}

 

BL, from the preceding output, there are two properties that are of use to help solve your exact scenario. The first property that seems promising is the filename property, and the second property worth investigating is the path property. I will look at both of them by displaying them via the Format-Table cmdlet. Both the command and the associated output are shown here:

PS C:\fso> dir c:\fso -I *.txt, *.log -R | Select-String fail | ft path, filename

 

Path                                          Filename

C:\fso\BackupLog.txt                   BackupLog.txt

C:\fso\BackupLog.txt                   BackupLog.txt

C:\fso\Install.log                         Install.log

C:\fso\Install.log                         Install.log

C:\fso\Install.log                         Install.log

C:\fso\Install.log                         Install.log

C:\fso\Install.log                         Install.log

C:\fso\Install.log                         Install.log

 

BL, it seems I may be on the right track; however, one problem is that from the preceding output, I see multiple matches for each file. If I were to simply choose the path property (and I must choose the path property because it includes both the file and the folder that contains the file) and open the file in Notepad, I would have multiple instances of Notepad displaying multiple copies of the same file.

To work around the problem of multiple matches, I use the list switch from the Select-String cmdlet. The revised command and associated output are shown here:

PS C:\fso> dir c:\fso -I *.txt, *.log -R | Select-String fail -list | ft path, filename

 

Path                                          Filename

C:\fso\BackupLog.txt                   BackupLog.txt

C:\fso\Install.log                         Install.log

 

Cool! Now, all I need to do is to walk through the collection of files that appear and open them in Notepad. I can do this by using the ForEach-Object cmdlet as shown here (the % character is an alias for the ForEach-Object cmdlet and dir is an alias for the Get-ChildItem cmdlet):

dir c:\fso -I *.txt, *.log -R | Select-String fail -list | % { notepad $_.path }

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

Image of command and associated output

 

BL, that is all there is to using the Select-String cmdlet to parse text files in a folder, and open the files that contain matches. This also ends Select-String Week. Join me tomorrow as I talk to various community members about starting and running a Windows PowerShell user group. I have two articles this weekend about working with Windows PowerShell user groups—both are important.

I invite you to follow me on Twitter and to join the Scripting Guys group on 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

 

 


Practical Tips for Starting a PowerShell User Group

$
0
0

Summary: In today’s blog post, Microsoft Scripting Guy Ed Wilson relates practical tips for starting and running a Windows PowerShell user group.

 

Microsoft Scripting Guy Ed Wilson here. Today, I am going to do something a little different. I asked some people to provide their suggestions for starting a Windows PowerShell user group. I have compiled their suggestions into today’s article.
 

Finding a place for your Windows PowerShell User Group to meet

Mark Schill, president of the Atlanta Windows PowerShell User Group:

“Finding a location wasn’t as hard as I thought it would be. For my Citrix group, we actually meet at the Microsoft office in Alpharetta. It is by far the best facility around. I originally made contact through a Microsoft contact, but now I just work directly with Spherion, the agency that manages their scheduling for rooms. They are really accommodating, but if you have an after-hours meeting, you have to find a Microsoft employee to sponsor and attend the meetings.

“On the other hand, for the Windows PowerShell group we did not have a local Microsoft employee to sponsor the meetings, and because all of the meetings occur after hours, we were unable to use the Microsoft office. I contacted our local New Horizons training facility through their website. They are supporting a number of Windows PowerShell user groups, and I was told to give them a call. I think they have a company policy encouraging them to support community user groups. We were able to work out the details, so we currently meet at the New Horizons facility.

Getting sponsors for your Windows PowerShell User Group

“Finding sponsors hasn’t been all that hard either. Hal Rottenberg and PowerShellCommunity.org have been extremely helpful putting me in touch with sponsors.

User Group Support Services (UGSS) is a must for Microsoft user groups. This could be a post of its own. After signing up your user group with UGSS, you should also sign up with GITCA.”

GITCA

  • All GITCA user groups are entitled to a free hosted Windows SharePoint Services website.
  • Every GITCA user group is eligible for a Live Meeting account at no cost.
  • GITCA members can take advantage of 50 percent off official Microsoft e-learning courses, thanks to an offer from Operitel.

UGSS

  • Every quarter, UGSS sends out a goodies box with lots of stuff.
  • You can request up to $1,000 in $500 chunks for your user group. (I used this to pay for pizza and drinks for meetings.)

Advertising the group

Mark Schill continues:

“For advertisement of the group, I have relied heavily on social media. I created a Twitter account for the user group, @ATLPUG. When I create events, I send out a notice via MailChimp to registered members as well as posting via @ATLPUG and my Facebook and LinkedIn accounts. The powershellgroup.org website also allows greater visibility for users looking for a user group.

Getting speakers

“Getting speakers has sometimes been hard to do. Most of the speakers have been found by reaching out to the Windows PowerShell community. MVPs have been a great resource for speaking and finding people who would be interested in speaking, but sometimes, it is hard to get scheduling worked out. There are also several key members of the group who have stepped up to speak about various topics. I am a little disappointed because I wish more of the group members themselves would present, but I think they often do not feel they have anything to offer the rest of the group.

Budgets

“I haven’t had many issues with budgeting. I have been using the UGSS funds to pay for pizza when we were without a sponsor. When sponsors are available, we have them pay for food and drinks or reimburse me. This keeps everything simple and we don’t have to worry about taxes. Some groups solicit donations from the attendees to pay for food.

Creating the website

“Joel Bennet (Jaykul) created a website for all of the Windows PowerShell user groups at http://powershellgroup.org/. You can simply send him email to request a subwebsite for your group. I have had some problems in the past with scheduling events, so I use Eventbrite for creating the meeting requests and MailChimp for tracking members and sending them email.”

Miscellaneous items

Be sure to let Hal and Jon at PowerScripting Podcast know about your user group, and they can announce it to solicit participation in your area. (The Scripting Wife is their scheduler, so I know more about their podcast than the others.) There’s also get-scripting; I know both Jonathan and Alan, so I am sure they will give you a plug. I have been on the Mind of Root podcast and know Steve personally. He is an awesome person and leads a user group in Milwaukee

You may want to consider advertising your meetings and recording the presentations. Twitter reaches a lot of people, and you can sometimes attract both speakers and sponsors by simply raising the profile of your group.

Two guys we met at the PowerShell Deep Dive who run an awesome Windows PowerShell User Group are Mike Pfeiffer and Jason Helmick. Mike just wrote a book about Exchange and Windows PowerShell, so he might be a good guest to have on your list for speakers. But beyond that they may have some pointers for you. Contact them via their AZPOSH website.

On the new Scripting Community page on the Script Center, we have a map that shows the user groups registered on powershellgroup.org. At the top of the page, we announce my upcoming in-person and virtual engagements. As soon as you have a user group webpage created and you have populated it with information, let me know so we will can add it to our Scripting Community page.

 

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

 

 

Mark Schill Discusses PowerShell User Groups

$
0
0

Summary: Guest Blogger Mark Schill discusses PowerShellGroup.Org and starting a user group.

 

Microsoft Scripting Guy Ed Wilson here. Our guest blogger today is Mark Schill. Mark is an IT veteran who for 13 years has specialized in Windows Server and Citrix technologies.

Mark started scripting with a Y2K project that required the creation of thousands of new NT domain accounts and has been automating ever since. He made the switch to Windows PowerShell in July 2007 with his first Citrix MFCOM script for publishing desktops, which is still in production to this day. In addition to scripting, Mark spends his workdays as an all-in-one C# developer, SQL Server database administrator, and Windows system administrator.

He is currently the president of the Atlanta PowerShell User Group and the Atlanta Citrix User Group as well as the vice president of the Virtual PowerShell User Group. When possible, Mark is hanging out with the Virtual PowerShell User Group on the #PowerShell IRC channel on irc.freenode.net. You can track Mark at his blog at http://www.cmschill.net/StringTheory, via email at Mark.Schill@cmschill.net, or on Twitter @meson3902.

Take it away, Mark!

 

My name is Mark Schill. Ed likes to introduce me as THE president of the Atlanta PowerShell User Group. Microsoft Tech∙Ed 2011 was here in my hometown of Atlanta, Georgia, and I was given a wonderful opportunity. I was invited by The Scripting Guy and his better half, The Scripting Wife, to assist in staffing the Scripting Guys’ booth. It was an incredible experience and I enjoyed meeting the many people who visited the booth. The photo below shows from left to right yours truly, Ed Wilson, and Teresa Wilson.

Photo of Mark Schill, Ed Wilson, and Teresa Wilson

The interest in Windows PowerShell user groups was tremendous .Many were interested in finding a group in their location, and others were interested in starting a user group. I met several local people who were surprised there was an Atlanta user group. Upon hearing this, I realized there is a great opportunity for me to help.

I have been presented with an opportunity to lead the effort in providing support to local user groups and the Windows PowerShell community as a whole. I am very excited and want to share a bit of my vision with you. I will be working with the PowerShell Community directors to provide a common community focus. With their support and guidance, we should be able to create an amazing ecosystem. Powershellgroup.org has served as the central location for Windows PowerShell user groups and will continue to be the focus point for this initiative.

The first task I will be focused on is upgrading and stabilizing the website. Joel Bennett has been doing an excellent job maintaining it, but we all know he already has way too much to do for one person. Moving forward, I will assume responsibility for the website. It does have a few issues, the most severe stemming from an issue with the hosting provider. Joel and I are currently working on migrating the website to a new hosting provider and upgrading to the latest site software. When complete, we will launch the new site with all the familiar components: calendar, events, registration, discussions, and a few new ones.

For user group evangelization, PowerShellGroup.org will provide a public API that will provide a complete user group listing and relevant information such as group contacts, meeting location, and meeting schedule. This API will allow anyone to provide a listing of user groups in their program and blog that will always be up to date with the information on the site. Users of the API will be able to use the data to generate their own maps; however, we will also have a map on the website that can be embedded so that visitors won’t have to create their own.

A fair number of users were interested in starting their own group, but seemed to be intimidated by the process. To assist these individuals, we will be creating a New User Group Support Kit.

Note   In the meantime, see yesterday’s Hey, Scripting Guy! Blog post for some of the information that we will be providing in the kit.

This kit will provide users everything they need to start a new user group. It will have a compilation of all the resources available to user groups, including where to get money for meetings, Live Meeting accounts, and swag for your meetings. We will also provide meeting location contacts and assist you with securing a location for your meetings. All you have to do is find the speakers and schedule the meetings.

For new and existing user groups, we have some great stuff planned to support your needs and make sure you have a chance to excel. On PowerShellGroup.org, we will provide the organizers of local Windows PowerShell user groups with their own private forum to chat with other leaders. This will allow everyone to share their knowledge, experience, and questions.

Another exciting feature will be a service for connecting speakers to user group leaders. We will maintain a registry of speakers who are interested in speaking at a user group meeting, whether in person or virtually. This will make finding speakers for your user groups extremely easy. The registry will also benefit speakers in that we will have a complete listing of user groups and their usual meeting times. Speakers sometimes like to do speaking tours, so this will allow them to efficiently schedule their speaking engagements. When a speaker is travelling to a city for business, they will be able to quickly see if a user group meets during their visit and offer their speaking services.

The items I listed in this article may take some time to implement, so your patience is appreciated. Though I am leading the effort, it is still driven and directed by the community. I welcome your comments and suggestions at feedback@powershellgroup.org.

 

Thank you, Mark!

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

 

 

Learn Four Ways to Kill a Process Using PowerShell and WMI

$
0
0

Summary: Microsoft Scripting Guy Ed Wilson shows four ways to kill a process by using Windows PowerShell and WMI.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I have been playing around with your scripts that explore WMI methods and WMI writable properties, but I am having problems calling the WMI methods. Can you help me?

—ET

 

Hey, Scripting Guy! AnswerHello ET,

Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife and I are super excited! Tomorrow night (August 9, 2011) we are going to appear at the Corpus Christi PowerShell User Group in Corpus Christi, Texas. This is their first meeting, and we hope to get them started out right. The group president, Mark Adam Carter, has been really busy getting everything set up for this meeting. If you are in the neighborhood, plan on stopping by. It will be a great meeting!

I wrote that series of articles you refer to as leadup to the 2011 Scripting Games. The series itself consisted of four articles:

ET, phone home. Just kidding. ET, the listing of all methods and writable properties from all WMI classes is a very good place to start, if you are looking to call some WMI methods. One reason for this is that the available classes varies from version to version of the operating system. In addition, the available classes will vary depending on which options are installed. The other thing to keep in mind is that some classes have added additional methods to classes in later versions of the operating system. Examples of this are the Enable and Disable methods that are available in the Win32_NetworkAdapter WMI class. These methods were added to Windows Vista. I wrote about these methods in How Can I Enable or Disable My Network Adapter? blog post.

There are actually several ways to call WMI methods in Windows PowerShell. One reason for this is that some WMI methods are instance methods, which means they only work on an instance of the class. Other methods are static methods, which means they do not operate on an instance of the class. For example, the Terminate method from the Win32_Process class is an instance method—it will operate only against a specific instance of the Win32_Process. If I do not have a reference to a process, I cannot terminate the process. On the other hand, if I want to create a new instance of a Win32_Process class, I do not grab a reference to an instance of the class. For example, I do not grab an instance of Calculator to create an instance of Notepad. Therefore, I need a static method that is always available.

ET, let me illustrate the first of these two principles—instance methods and static methods—with a short example (I will talk about static methods tomorrow). First, I create an instance of Notepad. I then use the Get-Process cmdlet to view the process (gps is an alias for the Get-Process cmdlet). This is shown here:

PS C:\> notepad

PS C:\> Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

 

 

__GENUS                    : 2

__CLASS                    : Win32_Process

__SUPERCLASS               : CIM_Process

__DYNASTY                  : CIM_ManagedSystemElement

__RELPATH                  : Win32_Process.Handle="588"

__PROPERTY_COUNT           : 45

__DERIVATION               : {CIM_Process, CIM_LogicalElement, CIM_ManagedSyste

                             mElement}

__SERVER                   : NEWMRED

__NAMESPACE                : root\cimv2

__PATH                     : \\NEWMRED\root\cimv2:Win32_Process.Handle="588"

Caption                    : notepad.exe

CommandLine                : "C:\Windows\system32\notepad.exe"

CreationClassName          : Win32_Process

CreationDate               : 20110802191720.281486-240

CSCreationClassName        : Win32_ComputerSystem

CSName                     : NEWMRED

Description                : notepad.exe

ExecutablePath             : C:\Windows\system32\notepad.exe

ExecutionState             :

Handle                     : 588

HandleCount                : 61

InstallDate                :

KernelModeTime             : 156001

MaximumWorkingSetSize      : 1380

MinimumWorkingSetSize      : 200

Name                       : notepad.exe

OSCreationClassName        : Win32_OperatingSystem

OSName                     : Microsoft Windows 7 Ultimate |C:\Windows|\Device\H

                             arddisk0\Partition2

OtherOperationCount        : 67

OtherTransferCount         : 198

PageFaults                 : 1362

PageFileUsage              : 1972

ParentProcessId            : 6324

PeakPageFileUsage          : 1972

PeakVirtualSize            : 72261632

PeakWorkingSetSize         : 5308

Priority                   : 8

PrivatePageCount           : 2019328

ProcessId                  : 588

QuotaNonPagedPoolUsage     : 7

QuotaPagedPoolUsage        : 149

QuotaPeakNonPagedPoolUsage : 7

QuotaPeakPagedPoolUsage    : 150

ReadOperationCount         : 0

ReadTransferCount          : 0

SessionId                  : 1

Status                     :

TerminationDate            :

ThreadCount                : 1

UserModeTime               : 0

VirtualSize                : 72261632

WindowsVersion             : 6.1.7601

WorkingSetSize             : 5435392

WriteOperationCount        : 0

WriteTransferCount         : 0

ProcessName                : notepad.exe

Handles                    : 61

VM                         : 72261632

WS                         : 5435392

Path                       : C:\Windows\system32\notepad.exe

 

After I have the instance of the notepad process I want to terminate, I have at least four choices:

  1. I can call the method directly using dotted notation (because there is only one instance of the notepad process).
  2. I can store the reference in a variable, and then terminate it directly.
  3. I can use the Invoke-WMIMethod cmdlet.
  4. I can use the [WMI] type accelerator.

You will notice that each time the method is called, a ReturnValue property is returned from the method call. This value is used to determine if the method completed successfully. Return codes are documented for the Terminate method on MSDN (each method will have its return codes detailed on MSDN):

PS C:\> (Get-WmiObject win32_process -Filter "name = 'notepad.exe'").Terminate()

 

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

 

PS C:\> notepad

PS C:\> $a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

PS C:\> $a.Terminate()

 

 

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

 

If I want to use the Windows PowerShell Invoke-WMIMethod cmdlet to call an instance method, I must pass a path to the instance to be operated upon. The easiest way to obtain the path to the instance is to first perform a WMI query, and then use the __RelPath system property. The __RelPath system property contains the relative path to the instance of the class. This is shown here:

PS C:\> notepad

PS C:\> $a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

PS C:\> $a.__RELPATH

Win32_Process.Handle="5676"

 

If I am working against a remote machine, I will want the complete path to the instance. The complete path includes the computer name, the WMI namespace, and the class and the key to the class. The complete path is shown in the __Path system property as shown here (do not get confused—the Win32_Process WMI class also contains a path property):

PS C:\> $a.__path

\\NEWMRED\root\cimv2:Win32_Process.Handle="5676"

 

As shown here, I first create an instance of the notepad process, use the Get-WmiObject cmdlet to retrieve that instance of the process, display the value of the __RELPATH property, and then call the Invoke-WmiMethod cmdlet. When calling the Invoke-WmiMethod cmdlet, I pass the path to the instance and the name of the method to utilize. This is shown in the following commands:

PS C:\> notepad

PS C:\> $a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

PS C:\> $a.__RELPATH

Win32_Process.Handle="2008"

PS C:\> Invoke-WmiMethod -Path $a.__RELPATH -Name terminate

 

 

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

 

Another way to call an instance method is to use the [WMI] type accelerator, which works with WMI instances. Therefore, if I pass a path to the [WMI] type accelerator, I can call instance methods directly. In the example that appears here, I start an instance of the notepad process. Next, I use the Get-WmiObject cmdlet to retrieve all instances (there is only one instance) of notepad. Next, I pass the value of the __RELPATH system property to the [WMI] type accelerator. This command returns all of the properties associated with Win32_Process (the same properties seen earlier in this article) for the specific instance of Win32_Process that is indicated by the __RelPath system property. I therefore select only the name property from the object. To this point, I have illustrated that I can retrieve a specific instance of a Win32_Process WMI class via the [WMI] type accelerator. Therefore, it is time to call the Terminate method. This technique is shown here:

PS C:\> notepad

PS C:\> $a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

PS C:\> [wmi]$a.__RELPATH | select name

 

name

----

notepad.exe

PS C:\> ([wmi]$a.__RELPATH).terminate()

 

 

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

 

This series of commands and their associated output are shown in the following figure.

Image of series of commands and associated output

ET, that is all there is to using WMI instance methods. WMI Week will continue tomorrow when I will talk about working with WMI Static methods.

 

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 Convert SDDL to Binary Format

$
0
0

Summary: Learn how to use Windows PowerShell to convert security descriptors to different formats.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I was reading through a listing of WMI methods recently, and I ran across a WMI class that looks interesting—Win32_SecurityDescriptorHelper. I think it will help me, because it seems like every security utility wants a different type of security token. But I am not sure how to use this thing; all I get are errors. Does this even work? Any help on your end?

—SH

 

Hey, Scripting Guy! AnswerHello SH,

Microsoft Scripting Guy Ed Wilson here. Tonight is the big night. They say everything is big in Texas, so let’s fill up the conference center for the inaugural meeting of the Corpus Christi PowerShell User group (south Texas). The Scripting Wife and I will be on hand for the event, and I have a really cool presentation planned.

Note   Four articles talk about producing a usable listing of WMI methods and properties:

 

When working with WMI and Windows PowerShell, it is common to think about using the Get-WmiObject cmdlet. Unfortunately, when using the Get-WmiObject cmdlet with the Win32_SecurityDescriptorHelper class, nothing happens. When I attempt to pipe the results to Get-Member, an error is produced. The two commands are shown here (gwmi is an alias for Get-WmiObject, and gm is an alias for Get-Member):

gwmi win32_SecurityDescriptorHelper #no output

gwmi win32_SecurityDescriptorHelper | gm #generates an error

The commands and associated output are shown here.

Image of commands and associated output

Now, I remember the discussion from yesterday’s blog post about calling WMI methods that there are both instance methods and static methods.

Therefore, I will use the Get-Member cmdlet and choose static members. I wonder what will happen then? I therefore use Get-WmiObject and this time request static members from Get-Member. The command is shown here (gwmi is an alias for Get-WmiObject, and gm is an alias for Get-Member):

gwmi win32_SecurityDescriptorHelper | gm –Static

The command and associated output appear here.

Image of command and associated output

Maybe this is not so strange. For example, nothing came back when I used Get-WmiObject Win32_SecurityDescriptorHelper So maybe Get-Member is not lying to me, and maybe there really is nothing with which to work. I look up the Win32_SecurityDescriptorHelper class on MSDN, but unfortunately, the page has very little information that is useful and no examples of using the class.

Next, I decide to look up the class in the Windows Management Instrumentation Tester (WbemTest). From WbemTest, I see that the Win32_SecurityDescriptorHelper is a dynamic class, and I see that there are many methods available from the class. This is shown in the following figure.

Image of Win32_SecurityDescriptorHelper being dynamic class with many methods

When I click the Instances button (sixth button from top on right side), I see that there are no instances available. I then click the Show MOF button (third button from top on right side), and I see that all methods are implemented. A method will only work if it is marked as implemented. For example, the Win32_Processor WMI class has two methods listed: Reset and SetPowerState. Unfortunately, neither method is implemented and therefore they do not work (in the case of Win32_Processor, the methods are defined on the abstract class CIM_LogicalDevice and are inherited). The MOF description for the Win32_SecurityDescriptorHelper WMI class is shown in the following figure.

Image of MOF description of Win32_SecurityDescriptorHelper

I also notice that each method is static. From yesterday’s article, I remember that static methods do use an instance of the WMI class. This is why the Get-WmiObject command does not work with Win32_SecurityDescriptorHelper because Get-WmiObject returns instances of the class. With this WMI class, there are no instances.

Perhaps the easiest way to work with the static WMI method is to use the [wmiclass] type accelerator. The SDDLToBinarySD method will translate a Security Descriptor Definition Language (SDDL) string into a binary byte array security descriptor (binary SD) format. The best way to talk about this technique is to walk through an example of converting an SDDL to a binary SD. First, I need to obtain an SDDL; I can do that by using the Get-Acl cmdlet. The first thing I do is give the Get-Acl the path to a file on my computer. I store the resulting object in the $acl variable. Next, I examine the SDDL associated with the file, by querying the SDDL property. These two lines of code are shown here:

$acl = Get-Acl C:\fso\BackupLog.txt

$acl.Sddl

The two commands and their associated output are shown here.

Image of two commands and associated output

To convert the SDDL to binary SD format, I use the [wmiclass] type accelerator and call the method directly while supplying a SDDL to the SDDLToBinarySD method. The syntax for the command is shown here:

([wmiclass]"Win32_SecurityDescriptorHelper").SDDLToBinarySD($acl.Sddl)

 

One thing that is a bit confusing is that in Windows PowerShell, generally double colons are required to call a static method. For example, to obtain the sine of a 45-degree angle, I use the sin static method from the math class:

[math]::sin(45)

But here in WMI, there appears to be no difference between calling a static method and calling an instance method. The command to convert the SDDL to binary SD and the default output are shown in the following figure.

Image of command to convert SDDL to binary SD, and default ouput

All the methods return both the returnvalue property that provides the status of the command and the specific output for the converted security descriptor. To retrieve only the BinarySD output, I can add that to the end of the method call. The syntax of this command is shown here:

 ([wmiclass]"Win32_SecurityDescriptorHelper").SDDLToBinarySD($acl.Sddl).BinarySD

 

One of the cool things that I can do with the static methods from the Win32_SecurityDescriptorHelper class is to convert a SDDL security descriptor into an instance of the Win32_SecurityDescriptor WMI class. The Win32_SecurityDescriptor WMI class is often used to provide security for various resources. For example, if I create a new share and I want to assign security to the new share, I will need to provide an instance of Win32_SecurityDescriptor. Using the SDDLToWin32SD method, I can use an SDDL to get the Win32_SecurityDescriptor I need. To illustrate using the SDDLToWin32SD method, I will use the Invoke-WmiMethod to perform the conversion. The following one-line command illustrates using the Invoke-WMIMethod cmdlet to call the SDDLToWin32SD method:

Invoke-WmiMethod -Class Win32_SecurityDescriptorHelper -Name SDDLToWin32SD -ArgumentList $acl.Sddl

The following figure illustrates calling the method and shows the returned data. The data is contained in the Descriptor property.

Image of calling the method and the returned data

The other WMI methods from this class behave in a similar mechanism, and therefore will not be explored.

 

SH, that is all there is to using static WMI methods. WMI Method Week will continue tomorrow when I will talk about using Windows PowerShell and WMI to terminate multiple processes. It is a really cool article. I think you will enjoy it. I literally create a hundred processes, and then terminate them.

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 and WMI to Terminate Multiple Processes

$
0
0

Summary: Learn how to use Windows PowerShell and WMI to terminate multiple processes.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am wondering about how to use WMI to work with multiple instances of a class. For example, if I have 50 processes running, and I want to kill all of them at the same time, I can easily do this using the Get-Process and Stop-Process cmdlets. What if I want to use WMI instead? What do I need to do?

—CT

 

Hey, Scripting Guy! AnswerHello CT,

Microsoft Scripting Guy Ed Wilson here. Last night’s PowerShell user group meeting in Corpus Christi, Texas was a lot of fun. This was their first meeting, and Marc Adam Carter, the chapter president did an awesome job putting things together! It is a lot of work to start a PowerShell users group, and I really appreciate his enthusiasm. The Scripting Wife and I really enjoyed speaking and hanging out with people who share our passion for Windows PowerShell. If you ask her (the Scripting Wife that is), she will tell you my favorite two things are Windows PowerShell and talking to IT pros. When I combine the two activities, I am a little hard to control. Once again, the Highway to PowerShell rocked the walls and heralded my presentation.

Suppose I have fifty copies of Notepad running—that’s right, fifty. Don’t ask what I am doing with fifty copies of Notepad running. It just seems to happen at times, particularly if I use the range operator, a ForEach-Object statement, and call Notepad. Now, if I use WMI and the Win32_Process class, I can filter out all processes except for Notepad and store the results in a variable. I can then use the count property to see how many instances of Notepad are running. Now, if I attempt to call the Terminate method, it will fail. But, my first indication that things are somewhat awry is that tab expansion for the Terminate method does not work. The reason for the failure is that I am dealing with an array instead of a single instance of a process. The commands I have used are shown here:

1..50 | % {notepad}

$a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

$a.Count

$a.terminate()

 

The commands and the associated output are shown here.

Image of commands and associated output

If I want to terminate all 50 instances of Notepad, I can use the instances that are stored in the $a variable, but I will need to walk through the array and call the Terminate method once for each instance of the process. To do this, I find it easiest to use the pipeline, and the Foreach-Object cmdlet to permit me to work with each instance. The Foreach-Object cmdlet has the % alias; therefore, it is really easy to use at the command line inside the Windows PowerShell console. Because I am going to terminate 50 processes, I do not want to clutter my console with 50 return codes, so I pipe the output to the Out-Null cmdlet (to discard the return values). After I have terminated the 50 processes, I use the Get-WmiObject cmdlet to query once again for instances of notepad.exe. No error is returned if no instances of the process appear. These two commands are shown here:

$a | % { ([wmi]$_.__RELPATH).terminate() | out-null }

Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

 

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

Image of commands and associated output

I can use the Invoke-WmiMethod cmdlet to terminate all 50 instances of Notepad. First, I create 50 instances of Notepad. Next, I use the Get-WmiObject cmdlet to retrieve all the instances of Notepad. Then I pipe the objects to the Foreach-Object cmdlet. In the Invoke-WMIMethod cmdlet, I use the inputobject parameter to receive the object upon which to work. The cmdlet is smart enough to retrieve the relative path upon which to work. The commands are shown here:

1..50 | % {notepad}

$a = Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

$a | % {Invoke-WmiMethod -Name terminate -InputObject $_ | out-null}

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

Image of commands and associated output

Of course, it is possible to simplify working with the Terminate method. If I have to perform a WMI query to return all instances of the notepad.exe process, why store the results into a variable only to pipe the results and call a method? I can cut out the intermediary, and call the method directly. In this example, I first create 50 instances of Notepad, and then I use the Get-WmiObject cmdlet to find all instances of the notepad.exe process. I then pipe the results to the ForEach-Object cmdlet (using the % alias) and call the Terminate method from the piped object. I then write all 50 return codes back to the $null variable. The last Get-WmiObject command is used to show that no instances of Notepad are left running. The commands are shown here:

1..50 | % {notepad}

$null = Get-WmiObject win32_process -Filter "name = 'notepad.exe'" | % {$_.Terminate() }

Get-WmiObject win32_process -Filter "name = 'notepad.exe'"

 

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

Image of commands and associated output

I can use exactly the same technique to work with multiple processes. I modify the command that creates 50 instances of Notepad, so that it also creates 50 instances of the calc.exe process. Next, I modify the WMI filter parameter so that it returns all instances of both Notepad and Calculator. The remainder of the command is exactly the same: I pipe the resulting WMI objects to the ForEach-Object cmdlet, and I call the Terminate method of each object as it crosses the pipeline. I store the return codes in the $null variable and therefore discard them and avoid cluttering the Windows PowerShell console. The commands are shown here (the second command is a single logical command; it is broken at the pipeline character for better display on this blog):

1..50 | % {notepad;calc}

$null = Get-WmiObject win32_process -Filter "name = 'notepad.exe' OR name = 'calc.exe'" |

% { $_.Terminate() }

There is no output from the above commands, and therefore no need for another screen shot.

 

CT, that is all there is to using WMI instance methods when working with multiple instances of the WMI class. WMI Method Week will continue tomorrow when I will continue talking about working with WMI methods.

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

 

 

Configure a Network Adapter to Wake a Computer Via PowerShell

$
0
0

Summary: Learn how to use Windows PowerShell to configure a remote computer's network adapter to wake the computer.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! At work, we have been trying to get our automation solution put into place. One problem is that on many of the computers, the network adapter is not configured to allow it to wake up the machine. This means that when someone shuts down the computer in the evening, that is it until the next day. I would like to be able to do maintenance at night when the users have gone home, but to do so, I need to be able to wake up the computer. I found an article on the Internet, but basically the article talked about hacking the registry, and I did not feel like I could trust the article. Can Windows PowerShell configure a computer’s network card to allow it to wake up the computer?

—TS

 

Hey, Scripting Guy! AnswerHello TS,

Microsoft Scripting Guy Ed Wilson here. One thing to keep in mind about Windows PowerShell is that it can only do what is available. In other words, if no mechanism exists for automating a particular problem, Windows PowerShell will not be able to automate a solution. Windows PowerShell can certainly make working with registry keys easier because of the existence of the registry provider, and it can make working with WMI classes easier as well. However, if the underlying technology does not expose the functionality, we are out of luck.

TS, in this particular case, I am not certain you are out of luck. I was looking over my list of WMI classes that have settable properties and implemented methods, and I ran across the MSPower_DeviceWakeEnable WMI class. This particular WMI class exists in the Root\WMI namespace, and is not documented on MSDN. Because the WMI class is not documented, this means that it is not supported. It does not mean that it will not work, but that it is not supported. As a matter of a fact, it does not work on my Windows 7 Ultimate 64-bit desktop computer; I believe that is because I have a custom network adapter card driver that supports port teaming for better throughput.

The WMI class does work on my 32-bit Windows 2008 server, which is using an inbox network card driver. Because the class is not documented, it means that I cannot tell you if the class exists on Windows XP or Windows Server 2003. I know it exists on Windows Server 2008 R2, Windows Server 2008, and Windows 7.

TS, I am going to show you how to use the MSPower_DeviceWakeEnable WMI class to enable the network card to wake the machine. If the technique does not work on all your devices, there is nothing I can do—the class is unsupported (although you might be able to use the powercfg utility to configure your network cards to wake up the computer).

The first thing I need to do is to store credentials for my remote WMI connection. Remember, I am working against a remote server. I use the Get-Credential cmdlet to retrieve the credentials for the remote computer, and I store the returned credential object in the $cred variable. This is shown here:

$cred = Get-Credential nwtraders\administrator

Next, I use WMI and the Get-WmiObject cmdlet to look for a network card on the remote server that is netenabled. This property of the Win32_NetworkAdapter class should return True only if the network interface is enabled. If there are multiple network cards that are enabled, you might want to query for the NetConnectionStatus of 2 (means that it is connected). If you are still having difficulity finding the correct network adapter, you might want to try the netconnectionid property. That is the name of the connection (for example “Local Area Connection”), and if you have named all your network adapters the same, it will be easy to pick them up with this property.

When I query the netenabled property on my remote server, only one network adapter is returned. The command and associated output are shown here:

PS C:\> gwmi win32_networkadapter -filter "netenabled = 'true'" -cn dc1 -cred $cred

ServiceName      : E100B

MACAddress       : 00:07:E9:7C:A7:5F

AdapterType      : Ethernet 802.3

DeviceID         : 6

Name             : Intel(R) PRO/100 VE Network Connection

NetworkAddresses :

Speed            : 100000000

 

Now I repeat the above command, and store the returned WMI object in a variable named $nic. This command is shown here:

$nic= gwmi win32_networkadapter -filter "netenabled = 'true'" -cn dc1 -cred $cred

I next query the MSPower_DeviceWakeEnable WMI class to see what type of date it returns. The WMI class resides in the Root\WMI namespace, so I need to specify the namespace parameter when making the query. I am still working on a remote machine, so I also specify the computername (alias is cn) and the credential (shortened to cred) parameters. The command and associated output appear here.

PS C:\> gwmi MSPower_DeviceWakeEnable -Namespace root\wmi -cn dc1 -cred $cred

 

__GENUS          : 2

__CLASS          : MSPower_DeviceWakeEnable

__SUPERCLASS     : MSPower

__DYNASTY        : MSPower

__RELPATH        : MSPower_DeviceWakeEnable.InstanceName="PCI\\VEN_8086&DEV_105

                   0&SUBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0"

__PROPERTY_COUNT : 3

__DERIVATION     : {MSPower}

__SERVER         : DC1

__NAMESPACE      : root\wmi

__PATH           : \\DC1\root\wmi:MSPower_DeviceWakeEnable.InstanceName="PCI\\V

                   EN_8086&DEV_1050&SUBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0

                   "

Active           : True

Enable           : True

InstanceName     : PCI\VEN_8086&DEV_1050&SUBSYS_01571028&REV_02\4&1c660dd6&0&40

                   F0_0

 

__GENUS          : 2

__CLASS          : MSPower_DeviceWakeEnable

__SUPERCLASS     : MSPower

__DYNASTY        : MSPower

__RELPATH        : MSPower_DeviceWakeEnable.InstanceName="ACPI\\PNP0303\\4&1506

                   bb2e&0_0"

__PROPERTY_COUNT : 3

__DERIVATION     : {MSPower}

__SERVER         : DC1

__NAMESPACE      : root\wmi

__PATH           : \\DC1\root\wmi:MSPower_DeviceWakeEnable.InstanceName="ACPI\\

                   PNP0303\\4&1506bb2e&0_0"

Active           : True

Enable           : False

InstanceName     : ACPI\PNP0303\4&1506bb2e&0_0

 

The InstanceName properties look weird, but they also look familiar. They look like the PNPDeviceID property from the Win32_NetworkAdapter WMI class. I can therefore use the PNPDeviceID property value from my network adapter and filter out only a matching instance from the MSPower_DeviceWakeEnable WMI class. So I take the PNPDeviceID property value and attempt to use it in a Where-Object command. Unfortunately, an error results. The command and associated output are shown here.

PS C:\> gwmi MSPower_DeviceWakeEnable -Namespace root\wmi | where {$_.instancena

me -match $nic.PNPDeviceID }

 

Bad argument to operator '-match': parsing "PCI\VEN_8086&DEV_1050&SUBSYS_015710

28&REV_02\4&1C660DD6&0&40F0" - Unrecognized escape sequence \V..

At line:1 char:82

+ gwmi MSPower_DeviceWakeEnable -Namespace root\wmi | where {$_.instancename -m

atch <<<<  $nic.PNPDeviceID }

    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

    + FullyQualifiedErrorId : BadOperatorArgument

 

Bad argument to operator '-match': parsing "PCI\VEN_8086&DEV_1050&SUBSYS_015710

28&REV_02\4&1C660DD6&0&40F0" - Unrecognized escape sequence \V..

At line:1 char:82

+ gwmi MSPower_DeviceWakeEnable -Namespace root\wmi | where {$_.instancename -m

atch <<<<  $nic.PNPDeviceID }

    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

    + FullyQualifiedErrorId : BadOperatorArgument

 

Bad argument to operator '-match': parsing "PCI\VEN_8086&DEV_1050&SUBSYS_015710

28&REV_02\4&1C660DD6&0&40F0" - Unrecognized escape sequence \V..

At line:1 char:82

+ gwmi MSPower_DeviceWakeEnable -Namespace root\wmi | where {$_.instancename -m

atch <<<<  $nic.PNPDeviceID }

    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

    + FullyQualifiedErrorId : BadOperatorArgument

 

Bad argument to operator '-match': parsing "PCI\VEN_8086&DEV_1050&SUBSYS_015710

28&REV_02\4&1C660DD6&0&40F0" - Unrecognized escape sequence \V..

At line:1 char:82

+ gwmi MSPower_DeviceWakeEnable -Namespace root\wmi | where {$_.instancename -m

atch <<<<  $nic.PNPDeviceID }

    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

    + FullyQualifiedErrorId : BadOperatorArgument

 

The problem, it seems, is that the PNPDeviceID property contains characters that have a meaning in a regular expression. The PNPDeviceID is shown here:

PS C:\> $nic.pnpdeviceid

PCI\VEN_8086&DEV_1050&SUBSYS_01571028&REV_02\4&1C660DD6&0&40F0

The special characters need to be escaped before submitting to the Regex engine. This could be a tedious problem. But, then I remembered there is an escape method from the REGEX class. The Regex.Escape method is documented on MSDN and is easy to use. It will escape any invalid character in a string, and permit easy use of that string. This is exactly what is needed here. I place the call to the escape method just in front of the PNPDeviceID property. I store the returned object in a variable called $nicPower and I query the variable to ensure I contain the proper network adapter. Notice that the status of the enable property is set to false. The command and associated output are shown here:

PS C:\> $nicPower = gwmi MSPower_DeviceWakeEnable -Namespace root\wmi -cn dc1 -cred $cred | where {$_.instancename -match [regex]::escape($nic.PNPDeviceID) }

PS C:\> $nicPower

 

__GENUS          : 2

__CLASS          : MSPower_DeviceWakeEnable

__SUPERCLASS     : MSPower

__DYNASTY        : MSPower

__RELPATH        : MSPower_DeviceWakeEnable.InstanceName="PCI\\VEN_8086&DEV_105

                   0&SUBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0"

__PROPERTY_COUNT : 3

__DERIVATION     : {MSPower}

__SERVER         : DC1

__NAMESPACE      : root\wmi

__PATH           : \\DC1\root\wmi:MSPower_DeviceWakeEnable.InstanceName="PCI\\V

                   EN_8086&DEV_1050&SUBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0

                   "

Active           : True

Enable           : False

InstanceName     : PCI\VEN_8086&DEV_1050&SUBSYS_01571028&REV_02\4&1c660dd6&0&40

                   F0_0

Now, all I need to do is to change the value of the Enable property from False to True. After I have done that, I need to call the Put method from the base WMI object so that I write the changes back to the WMI database. These two commands and their associated output are shown here:

PS C:\> $nicPower.Enable = $true

PS C:\> $nicPower.psbase.Put()

 

 

Path          : \\dc1\root\wmi:MSPower_DeviceWakeEnable.InstanceName="PCI\\VEN_

                8086&DEV_1050&SUBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0"

RelativePath  : MSPower_DeviceWakeEnable.InstanceName="PCI\\VEN_8086&DEV_1050&S

                UBSYS_01571028&REV_02\\4&1c660dd6&0&40F0_0"

Server        : dc1

NamespacePath : root\wmi

ClassName     : MSPower_DeviceWakeEnable

IsClass       : False

IsInstance    : True

IsSingleton   : False

 

It takes a reboot for the changes to take effect. Because I am working remotely and using WMI, I decide to use the Reboot method from the Win32_OperatingSystem WMI class. When calling the Reboot method from Win32_OperatingSystem, it seems to be a bit confusing. For example, if I use the [WMICLASS] type accelerator, the Reboot and Shutdown methods appear as shown here:

PS C:\> [wmiclass]"Win32_OperatingSystem"

 

   NameSpace: ROOT\cimv2

 

Name                                       Methods                       Properties

Win32_OperatingSystem             {Reboot, Shutdown         {BootDevice, BuildN...

 

But, when I attempt to use the Invoke-WmiMethod cmdlet to reboot the computer, an error occurs. The error is shown in the following figure.

Image of error shown when attempting to use Invoke-WmiMethod to reboot computer

Before I actually reboot the system, I connect via Remote Desktop and view the status of the network adapter. As shown in the following figure, it is not configured to allow the device to wake the computer.

Image of network adapter not configured to allow device to wake computer

Okay, I have verified the baseline configuration. Now I need to figure out why my reboot command did not work. The reason it failed is because the Reboot method is an instance method, not a static method. The easiest way to reboot the machine is to use the Get-WmiObject cmdlet, and call the method directly from the resulting object. This command and its associated output are shown here:

PS C:\> (gwmi win32_operatingsystem -CN dc1 -cred $cred).reboot()

  

__GENUS          : 2

__CLASS          : __PARAMETERS

__SUPERCLASS     :

__DYNASTY        : __PARAMETERS

__RELPATH        :

__PROPERTY_COUNT : 1

__DERIVATION     : {}

__SERVER         :

__NAMESPACE      :

__PATH           :

ReturnValue      : 0

 

After waiting an indeterminable amount of time, the remote domain controller is back up and running. I connect once again with Remote Desktop to see if my changes actually took. As shown in the following figure, it worked.

Image of change having been applied

 

The complete sequence of commands could be placed easily in a single script and used to perform this configuration change in an automated fashion. Here are the commands:

EnableNicToWakeMachine.ps1

$cred = Get-Credential nwtraders\administrator

$nic= gwmi win32_networkadapter -filter "netenabled = 'true'" -cn dc1 -cred $cred

$nicPower = gwmi MSPower_DeviceWakeEnable -Namespace root\wmi -cn dc1 -cred $cred |

  where {$_.instancename -match [regex]::escape($nic.PNPDeviceID) }

$nicPower.Enable = $true

$nicPower.psbase.Put()

(gwmi win32_operatingsystem -CN dc1 -cred $cred).reboot()

 

TS, that is all there is to using WMI to set the ability of a NIC to wake up a computer. This also concludes WMI Method Week. Join us tomorrow when we have a guest blog article by Robert Robelo.

 

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 Work with Pictures in Microsoft Excel

$
0
0

Summary: Guest Blogger Robert Robelo shows how to use Windows PowerShell to work with images in Microsoft Excel.

 

Microsoft Scripting Guy Ed Wilson here. Our guest blogger today is Robert Robelo. First, I want to give Robert a chance to tell us a little bit about himself.

I live in Nicaragua. I have been an incomplete quadriplegic (paralyzed from the chest down, but have limited mobility in upper limbs) for more than twenty years. Because of that I have not had a job since then. Before I got shot, I was an officer in the Nicaraguan Resistance Army, better known as Contras. One of my multiple responsibilities was to train, support, and maintain the IT structure (guess that would be a chief information officer), which was no more than twenty individual machines. My other responsibilities included intelligence, counterintelligence, and friendly forces liaison officer; obviously, before becoming an officer, I was an infantry and artillery soldier. I later became a sniper.

Robert is going to talk to us about working with images in Microsoft Excel via Windows PowerShell. Take it away, Robert!

 

Inserting an image in an Excel spreadsheet with Windows PowerShell is not an everyday chore for IT pros, but there very well may come a time where it would be useful to know how to do such a task.

An Excel spreadsheet has a Shapes property; it holds a collection of objects in the drawing layer, such as AutoShapes, freeforms, OLE objects, and images. To insert an image in the spreadsheet, use the Shapes collection's AddPicture method. This method takes seven required arguments, which are shown in the following figure.

The AddPicture method returns a Shape object, which should be assigned to a variable. The Shape can then be relocated, resized, rotated, transformed, cropped, formatted, duplicated, and so on.

Let's fire up an Excel instance, get a new workbook, and grab the ever-present Sheet1. Before we do that, let's set up a few Constant variables. First, the MsoTriState constants that apply to the AddPicture method and then a couple Constant variables of our own. These will hold the default width and height of a cell; they can be very useful to place and size the image we are going to insert. However, they are not necessary if you prefer to place and size the image with greater accuracy. In this sample, we will place the image's upper left corner in cell C3's upper left corner—that is two cells from the left and two cells from the top of the sheet, and we will make the image two cells wide and four cells tall. (Note   The image path is set to C:\Ensign.jpg; you can substitute this with your own image or download the .zip file that contains that image as part of a test kit. Read more below.)

# Excel Constants

# MsoTriState

Set-Variable msoFalse 0 -Option Constant -ErrorAction SilentlyContinue

Set-Variable msoTrue 1 -Option Constant -ErrorAction SilentlyContinue

 

# own Constants

# cell width and height in points

Set-Variable cellWidth 48 -Option Constant -ErrorAction SilentlyContinue

Set-Variable cellHeight 15 -Option Constant -ErrorAction SilentlyContinue

 

$xl = New-Object -ComObject Excel.Application -Property @{

 Visible = $true

 DisplayAlerts = $false

}

$wb = $xl.WorkBooks.Add()

$sh = $wb.Sheets.Item('Sheet1')

 

# arguments to insert the image through the Shapes.AddPicture Method

$imgPath = 'C:\Ensign.jpg'

$LinkToFile = $msoFalse

$SaveWithDocument = $msoTrue

$Left = $cellWidth * 2

$Top = $cellHeight * 2

$Width = $cellWidth * 2

$Height = $cellHeight * 4

 

# add image to the Sheet

$img = $sh.Shapes.AddPicture($imgPath, $LinkToFile, $SaveWithDocument,

 $Left, $Top, $Width, $Height)

$xl.Speech.Speak('Add an image to the Sheet through the Add Picture Method.')

Pretty easy, isn't it? You can examine the Shape object with the Get-Member cmdlet to see the different methods and properties it has. There are too many to explore in one blog post anyway.

Now, let's close the workbook without saving it, quit Excel, and clean up its insubordinate instance with the Remove-ComObject function. Basically, the Remove-ComObject function collects all variables that were assigned System.__ComObject objects within the caller scope and are not Constant or Read-Only variables:

# close without saving the workbook

$wb.Close($false)

$xl.Quit()

Remove-ComObject

There are many techniques that attempt to release these defiant System.__ComObject objects. Some do get the job done eventually, but they look chaotic or involve repeated calls to Runtime.Interopservices.Marshal's ReleaseComObject method. Using the Remove-Variable cmdlet the traditional way to get rid of these vestigial System.__ComObject objects will not suffice either, but using it as I do in the Remove-ComObject function, this capricious cmdlet does its job very well with a little help from its friends.

First, the Remove-ComObject function briefly pauses for half a second to let Windows PowerShell mark the System.__ComObject object as Disposed and ready to be collected by the Garbage Collector. Next, a variable is set with the Management.Automation.ScopedItemOptions ReadOnly and Constant enumerations. This variable will be the second half of the filter that will be applied in the following step. Then, the Get-Variable cmdlet fetches all variables in the caller scope (-Scope 1) and pipes them to the Where-Object cmdlet, where a filter is applied to exclude Constant or Read-Only variables, but whose value are of type System.__ComObject. This first half of the filter is done by comparing the value's PSTypeNames collection against the string System.__ComObject with the Contains operator. The unfortunate and rebellious variables that go through are then piped to the Remove-Variable cmdlet and are slyly shredded to oblivion. Finally, the Garbage Collector gets a call to sanitize the area and collect the scraps.

The Remove-ComObject function is an advanced function because it has common parameters, which permits the user to use its Verbose flag to divulge which variables were removed. I do want to point out a very important fact: the Remove-ComObject function removes all variables declared in the function's caller scope. It is safer to use it within scripts, functions, or modules, but you can also call it from the command line. Just beware that you may wipe out other System.__ComObject objects. This is the reason the function excludes Constant or Read-Only variables. It is up to you to declare those precious variables as such.

I have set up a test that demonstrates the efficacy of my technique to successfully release System.__ComObject objects before they become ghosts in your machine. You can download the test kit here; the results are displayed in the console. The test will show RAM usage and Excel processes running before and after the execution of two scripts, one that cleans up with a Remove-ComObject call and another that does not. These scripts also demonstrate other image manipulation that you might find interesting. Unzip the contents to your favorite test directory, run TestExcelDemo.ps1, watch and listen. It is less than five minutes. Excel 2010 or Excel 2007 is required.

 

Robert, I want to thank you for writing a really cool script, and for sharing your Windows PowerShell goodness 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 Audit and Install Windows Patches

$
0
0

Summary: Learn how to use a free Windows PowerShell module to audit and install patches on Windows systems.

 

Microsoft Scripting Guy Ed Wilson here. Today, I am proud to present a guest blog post written by Boe Prox. Boe has written a really cool module to audit and install software patches on Windows systems.

Boe is currently a senior systems administrator with BAE Systems. He has been in the IT industry since 2003 and has spent the past three years working with VBScript and Windows PowerShell. He looks to script whatever he can, whenever he can. He is also a moderator on the Hey, Scripting Guy! Forum. You can check out his blog and his projects: PoshWSUS and PoshPAIG.

 

<BoeProxDisclaimer>This post does not contain any code in it. It does, however, show examples of what I have been able to put together using Windows PowerShell to create an application to manage the patching of servers in an enterprise environment. Tomorrow’s post will contain more code in it as I discuss some issues I ran into and how I resolved them.</BoeProxDisclamer>

Something that I have been working on for a little while as a work project is a way for other users in my shop to be able to patch our servers during a downtime without actually having to log into each server remotely to install the patches that have been downloaded from the local WSUS server. 

Initially, I wrote two sets of Windows PowerShell scripts: one to audit for patches that were downloaded to each server and waiting to be installed, and another that would install those patches on the server remotely. Though this is a great solution for a couple other folks and me who are familiar with Windows PowerShell, it is not the “complete solution” that we were looking to use with everyone else in my shop. We needed something that would make it easy for anyone to use (a GUI) and still perform the same tasks as the original scripts I wrote.

Enter the PowerShell Patch Audit/Installation GUI (PoshPAIG).

Image of PoshPAIG interface

Rather than go into a lot about how I built PoshPAIG and why I made it the way I did, I am going to go more into some of the new features that I implemented for the latest version, 1.6. If you wish to learn more about what I did to initially build this utility, go to this blog post. My next post tomorrow will also go into some issues I ran into during the build and what I was able to do to resolve them.

Running the utility

First download PoshPAIG. From there, unzip the file to wherever you wish. You can then open a Windows PowerShell console, navigate to the directory structure, and run the Start-PoshPAIG.ps1 script to start the utility.

No more double-clicking a system to run an operation

One of the first changes I made was the removal of double-clicking on a system in the server list to perform the specified operation. Instead of doing that, you can now right-click a system and bring up a shortcut menu to select a few different items, such as removing a server, viewing the WindowsUpdatelog.log, viewing installed updates and performing other operations against the remote system. Clicking Run in the shortcut menu will perform the specified operation that is designated below the server list (Audit, Install, Test Network Connection, or Reboot).

Image of shortcut menu that appears when right-clicking a system

Adding multiple computers using the Add Server menu

Originally, the Add Server menu only let you add one system at a time. While this is okay, I felt that it needed the ability to add more than one system at a time. So with that, I’ve made it so you can add more than one computer by separating the names with a comma. Simply right-click the server list window, click Add Server, type each system name, and then click OK.

The following figure shows the UI before clicking OK.

Image of UI before clicking OK

After clicking OK, the servers are then added to the server list, as shown in the following figure.

Image of UI after clicking OK

Support for operations against multiple systems at a time

One of my most requested features was the ability to perform the operations (audit, install, etc.) against multiple systems at a time instead of against only one system at a time. Doing this allows for a much quicker process of completing whichever operation that you decide to do; otherwise, the operation could take a much longer time than you would expect.

As you can see from another new feature—the Notes column—all the systems are being audited for patches that have been downloaded from the WSUS server. As each system is finished, it will be updated accordingly on the server list with the number of patches found. Depending on the operation you choose, the Notes column and other columns will be affected as well based on the decision. During these operations, if the system is not reachable on the network, the Notes column will report it as being offline.

Image of Notes column with Completed and Offline statuses

By default, the number of systems that will be run against at a time is 20. You can adjust this in the Start-PoshPAIG.ps1 script by modifying the $maxconcurrentjobs variable on line 37 to whatever you feel is an appropriate value.

Sort columns when clicked

Something that should have been in the first version but unfortunately was not as easy to implement as I would have thought (more on this in tomorrow’s post) was the ability to sort a column when clicked. So in this latest version, you can now sort a column by clicking it. The following figure shows sorting the Audited column so that the system with the most patches required is first.

Image of Audited column sorted by systems needing most patches at top

 

Tracked reboot of systems

Another option I added was the ability to send a reboot command to the remote systems; it will continue to monitor the system until it is back online. To avoid having too many systems being rebooted at one time, I have hard-coded a limit of five systems at a time to be rebooted. If a machine has not been reported as being back online within five minutes, it will be registered as being offline and will need more investigation into the system to see why it has not came back online.

To do this, first select the Reboot option below the server list, and you can choose to run the command against every system by clicking Run. Or you can run the command against a specific computer or computers by selecting them from the server list, right-clicking the server list, and then clicking Run.

Image of running command against specific computer

A warning is first presented advising the user that the computer will be rebooted if the user chooses to continue.

Image of reboot warning

Clicking Yes will continue the reboot process. When completed, you will see the Completed note in the Notes column or the Offline note.

Ping sweep

Another option implemented in version 1.6 is performing a “ping sweep” of all the systems in the server list. This is done by selecting the Test Network Connection check box, clicking Run or selecting the systems, and clicking Run from the menu when right-clicking the server list.

Image of testing network connection

The Notes column shows that the network check is occurring.

Image of results of network connection check

As you can see, two systems are online and the rest are offline. Note that the total time to perform this was just less than 13 seconds.

View WindowsUpdate.log for troubleshooting

I thought that this would be a nice and obvious addition to this utility. One big caveat is that this can only be run against one system at a time. Parsing and using Out-GridView to display the output against a file that could possibly contain several thousand lines of information would slow things down quite a bit. Just right-click a system, click WindowsUpdateLog in the shortcut menu, and then click one of the four options (Last 25, Last 50, Last 100, and Entire Log) to have the utility grab the remote log and display it.

Image of options for viewing WindowsUpdateLog

In this instance, I selected the last 50 lines from the WindowsUpdatelog.log to view.

Show currently installed updates

While not necessarily needed, I figured I would add an option to view the currently installed updates on one or more remote systems. Just select the systems, right-click Installed Updates, and then click View Installed Updates in the shortcut menu.

Image of View Installed Updates option

After all of the updates have been gathered, you can then view the installed updates on each system.

Image of viewing installed updates on each system

Wrap-up

I hope everyone enjoyed this post displaying some of the new features of my latest project. This is a work in progress and will have more releases in the future. If you have any feature requests or any bugs that you find, be sure to log them in the Issue Tracker on CodePlex.

Tomorrow, I will address some issues I ran into while creating this utility and the steps I took to resolve them. I promise you will see some code in that post.

 

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

 

 

Lessons Learned While Writing the PowerShell PoshPAIG Module

$
0
0

Summary: Guest Blogger Boe Prox shares lessons learned while writing the Windows PowerShell PoshPAIG module.

 

Microsoft Scripting Guy Ed Wilson here. Today, we have part two of Boe Prox’s article about his module to audit and update Windows systems. See yesterday's blog post for information about Boe as well as for a detailed article about features and use of the module. Here’s Boe.

 

In the previous post, I introduced several new features to my latest project, PoshPAIG, that greatly improved upon the previous release and allows anyone to patch systems remotely via a graphical user interface (GUI).

As with any script you write, there most likely will be some sort of issue you run into where it doesn’t run properly the first time, bugs are discovered while others are running it, or you think of a better way to make the code run. Creating my utility is by no means any different.

 

Use of XAML (Extensible Application Markup Language)

While I was about halfway into the build of the initial version of PoshPAIG, I switched my code from creating the UI by creating all of the objects for the controls in Windows PowerShell to writing the XAML code using a Here-String and then importing the code using Windows PowerShell. In the example below you will see both styles that produce the exact same output.

Non-XAML

#Load Required Assemblies

Add-Type –assemblyName PresentationFramework

Add-Type –assemblyName PresentationCore

Add-Type –assemblyName WindowsBase

[array]$colors = "red","green","black","gray","blue","yellow","purple"

##Build the GUI

$window = New-Object System.Windows.Window

$Window.Title = "Colors"

$Window.ResizeMode = "NoResize"

$Window.WindowStartupLocation = "CenterScreen"

$Window.SizeToContent = "WidthAndHeight"

$Window.ShowInTaskbar = "True"

$textblock = New-Object Windows.Controls.TextBlock

$textblock.Text = "Click the button to change the text below"

$textblock.Width = '150'

$textblock.Foreground = "Black"

$textblock.TextWrapping = 'Wrap'

$stackpanel = New-Object Windows.Controls.StackPanel

$Button = New-Object Windows.Controls.Button

$Button.Content = "Push Me"

$Button.Width = '60'

$label = New-Object Windows.Controls.Label

$label.Content = "Black"

$label.Foreground = "Black"

[array]$controls = $textblock,$button,$label

ForEach ($control in $controls) {

    $stackpanel.Children.Add($control) | Out-Null

    }

$window.Content = $stackpanel

##Events

#Button Click

$Button.Add_Click({

    $color = Get-Random $colors

    $label.Content = $color

    $label.Foreground = $color

    })

$Window.showdialog()

 

XAML Version

#Load Required Assemblies

Add-Type –assemblyName PresentationFramework

Add-Type –assemblyName PresentationCore

Add-Type –assemblyName WindowsBase

[array]$colors = "red","green","black","gray","blue","yellow","purple"

#Build the GUI

[xml]$xaml = @"

<Window

    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'

    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'

    x:Name='Window' Title='Colors' ResizeMode = 'NoResize' WindowStartupLocation = 'CenterScreen'

    SizeToContent = 'WidthAndHeight' ShowInTaskbar = 'True'>

    <StackPanel>

        <TextBlock  Text = 'Click the button to change the text below' Foreground = 'Black' TextWrapping = 'Wrap' Width = '150'/>

        <Button x:Name = 'Button' Content = 'Push Me' Width = '60'/>

        <Label x:Name = 'Label' Content = 'Black' Foreground = 'Black' />

    </StackPanel>

</Window>

"@

$reader=(New-Object System.Xml.XmlNodeReader $xaml)

$Global:Window=[Windows.Markup.XamlReader]::Load( $reader )

##Connect to Controls

$label = $Window.FindName('Label')

$Button = $Window.FindName('Button')

##Events

#Button Event

$Button.Add_Click({

    $color = Get-Random $colors

    $label.Content = $color

    $label.Foreground = $color

    })

$Window.showdialog()

 

The following figures show what you get from both of these. Nothing spectacular, but you get the idea.

Image of results of non-XAML code

 Image of results of XAML code

You might be thinking, “Big deal, Boe. The Non-XAML version had 37 lines of code but the XAML version had 32 lines.” And you would be right. Even though the difference for this small UI is only a few lines of code, imagine the increase of code on a more complex UI. Before I made the switch to XAML, I found that the difference was at least 80 lines of code. I would imagine that with all of my latest additions, it would be more than 100 lines of code. Of course, using ShowUI would reduce the amount of code even more so, but I chose to leave that out as my utility does not currently utilize it yet. Another thing I like about using XAML is how I can completely set up my UI, and then focus on connecting the controls needed and configuring the associated events.

 

UI freezing when performing an operation/Operation performed on one system at a time

Something that was becoming a pain when running this utility initially was that whenever a user would run an operation, the GUI would lock up and act as though it had stopped responding. Even though stuff was still happening (I could tell because I used a lot of Write-Verbose in my code to track what was happening), having the UI freeze would cause the user to have the false sense of a failure occurring. While not an issue per se, the idea of the operation performing against one system at a time could be seen as a major waste of time and would completely deter most folks from wanting to use it as an alternative to other means. This probably took the most of my time to figure out a usable solution that would solve both of these issues. Fortunately, the Windows PowerShell team came out with an excellent blog posting that just happened to fit exactly what I was looking for. Below is a snippet of the code I am using to perform the remote patch installations:

The first piece checks to see if there are systems available on which to install patches and if psexec.exe exists. From there I gather all of the servers in the server list and add them to a queue to be passed into the InstallJobFromQueue function, but create only as many jobs as I specified. In this case, I have a limit of 20 jobs at a time.

    If ($Global:Listview.ItemsSource.Count -gt 0) {

        $Global:ProgressBar.Maximum = $Global:Listview.ItemsSource.count

        $Listview.ItemsSource | ForEach {$_.Notes = $Null}

        #Install Patches

        If ($InstallRadio.isChecked) {

            If (Test-Path psexec.exe) {  

                $runbutton.IsEnabled = $False    

                $Global:StatusTextBox.Foreground = "Black"

                $Global:StatusTextBox.Text = "Installing Patches for all servers...Please Wait"  

                $Global:updatelayout = [Windows.Input.InputEventHandler]{ $Global:ProgressBar.UpdateLayout() }

                $Global:Start = Get-Date

 

                [Float]$Global:ProgressBar.Value = 0

 

                # Read the input and queue it up

                $queue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )

 

                foreach($item in $Global:Listview.ItemsSource | Select -Expand Computer)

                {

                    $queue.Enqueue($item)

                }

 

                # Start up to the max number of concurrent jobs

                # Each job will take care of running the rest

                for( $i = 0; $i -lt $maxConcurrentJobs; $i++ )

                {

                    InstallJobFromQueue

                }

                }

            Else {

                Write-Verbose "PSExec not in same directory as script!"

                $Global:StatusTextBox.Foreground = "Red"

                $Global:StatusTextBox.Text = "PSExec.exe missing from $($pwd)!"        

                }           

            $server = $Null

            }

 

The InstallJobFromQueue function takes the items in the queue and passes them into this function, which will continue to update itself until there are no more items in the queue.

#Function to install patches in background

Function Global:InstallJobFromQueue

{

    if( $queue.Count -gt 0)

    {

        $server = $queue.Dequeue()

        $Global:Listview.DataContext.Rows.Find($server).Notes = "Installing Patches"

        $j = Start-Job -Name $server -ScriptBlock {

                param($server,$location)

                Set-Location $location

                . .\Install-Patches.ps1

                Install-Patches -Computername $server

            } -ArgumentList $server,$Global:path

        Register-ObjectEvent -InputObject $j -EventName StateChanged -Action {

            #Declare value for server to be updated in grid

            $serverupdate = $eventsubscriber.sourceobject.name

            $Global:ProgressBar.Value++

            $Global:Window.Dispatcher.Invoke( "Render", $Global:updatelayout, $null, $null)       

 

            [Array]$Global:Data = Receive-Job -Job $eventsubscriber.sourceobject |

                Select Computer,Title,KB,IsDownloaded,Notes

            Write-Verbose "Updating: $($eventsubscriber.sourceobject.name)"

            If ($Data[0].Title -eq "NA") {

                Write-Verbose "No updates to install"

                $Global:Listview.DataContext.Rows.Find($serverupdate).Installed = 0

                $Global:Listview.DataContext.Rows.Find($serverupdate).Notes = "Completed"

                }

            Else {

                [array]$good = $Data | Where {$_.Notes -ne "Failed to Install Patch"}

                If ($good.count -lt 1) {

                    $Global:Listview.DataContext.Rows.Find($serverupdate).Installed = 0

                    }

                Else {

                    $Global:Listview.DataContext.Rows.Find($serverupdate).Installed = $good.Count

                    }

                $Global:Listview.DataContext.Rows.Find($serverupdate).InstallErrors = ($Data.Count - $good.Count)

                $Global:Listview.DataContext.Rows.Find($serverupdate).Notes = "Completed"

                }

            Write-Verbose "Updating Patch Report"

            [array]$Global:patchreport += $Global:Data

            Write-Verbose "Removing: $($eventsubscriber.sourceobject)"           

            Remove-Job -Job $eventsubscriber.sourceobject

            Write-Verbose "Unregistering: $($eventsubscriber.SourceIdentifier)"

            Unregister-Event $eventsubscriber.SourceIdentifier

            Write-Verbose "Removing: $($eventsubscriber.SourceIdentifier)"

            Remove-Job -Name $eventsubscriber.SourceIdentifier

            If ($queue.count -gt 0 -OR (Get-Job)) {

                Write-Verbose "Running InstallJobFromQueue"

                InstallJobFromQueue

                }

            ElseIf (-NOT (Get-Job))

            {

                [Float]$Global:ProgressBar.Value = $Global:ProgressBar.Maximum

                $End = New-Timespan $Start (Get-Date)                    

                $Global:StatusTextBox.Text = "Completed in: {0}" -f $end

                $Global:Runbutton.IsEnabled = $True

            }           

        } | Out-Null

        Write-Verbose "Created Event for $($J.Name)"

        }

}

 

Because I didn’t want to kick off the last jobs in the queue and move immediately into a “Completed” status, I made sure that each time a job completed, it would clean itself up by removing the job and its related event monitoring jobs. Doing it this away allowed me to use an If/Else statement to monitor the job queue with Get-Job. When the job queue has reached 0, it knows that everything is done and the utility can show a “Completed” status. All during this time, it will continuously update each server in the server list with the number of patches installed and any number of patches that had errors during the installation. Something I didn’t count on and am going to use this to segue into is…
 

Server list data not updating when jobs are completed

As it turns out, the issue with what I was able to achieve by configuring some multithreading using multiple background jobs is that I noticed that none of the data was updating on the server list. Again, using Write-Verbose heavily in my code allowed me to see that stuff was indeed happening. When I stopped the utility, I checked the variable holding the data table in the server list and saw that the data was there, but was not displaying like it should. My solution was found after a little trial and error: set up a timer on the window to automatically refresh the window every five seconds. By doing this, I have allowed the UI to allow the background jobs to update the data table on the server list. The code I used to make this happen is shown here:

#Timer Event

$Window.Add_SourceInitialized({

    #Create Timer object

    Write-Verbose "Creating timer object"

    $Global:timer = new-object System.Windows.Threading.DispatcherTimer

    #Fire off every 5 seconds

    Write-Verbose "Adding 5 second interval to timer object"

    $timer.Interval = [TimeSpan]"0:0:5.00"

    #Add event per tick

    Write-Verbose "Adding Tick Event to timer object"

    $timer.Add_Tick({

        [Windows.Input.InputEventHandler]{ $Global:Window.UpdateLayout() }

        Write-Verbose "Updating Window"

        })

    #Start timer

    Write-Verbose "Starting Timer"

    $timer.Start()

    If (-NOT $timer.IsEnabled) {

        $Window.Close()

        }

    })

 

 Sorting columns

Something that I take for granted in a UI but quickly realized was not an easy thing to implement is the ability to sort a column just by clicking the column header. By not easy to implement, I mean that as a nondeveloper it was not as obvious to me what the solution was until I had done enough research and some trial and error to really get it figured out. The trick was using the SortDescription property for a ListView control. The first thing I had to do was to define all of my column headers in my server list using the following XAML code using GridViewColumnHeader. The following code is a snippet for the ComputerColumnHeader showing the XAML and the subsequent Windows PowerShell code to allow sorting:

                    <GridViewColumn x:Name = 'ComputerColumn' Width = '100' DisplayMemberBinding = '{Binding Path = Computer}'>

                        <GridViewColumnHeader x:Name = 'ComputerColumnHeader'>

                        Computer

                        </GridViewColumnHeader>

                    </GridViewColumn>

 

#Connect to ComputerColumnHeader Control Event

$ComputerColumnHeader = $Global:Window.FindName("ComputerColumnHeader")

 

#Computer columns sort

$ComputerColumnHeader.Add_Click({

    $Listview.Items.SortDescriptions.Clear()

    If ($compsort -eq "descending")  {

        Write-Verbose "Sorting Ascending via Computer Column"

        $comp_ascend = New-Object System.ComponentModel.SortDescription("Computer","Ascending")

        $Listview.Items.SortDescriptions.Add($comp_ascend)

        $Listview.Items.Refresh()

        $compsort = "ascending"

        }

    ElseIf ($compsort -eq "ascending")  {

        Write-Verbose "Sorting Descending via Computer Column"

        $comp_descend = New-Object System.ComponentModel.SortDescription("Computer","Descending")

        $Listview.Items.SortDescriptions.Add($comp_descend)

        $Listview.Items.Refresh()

        $compsort = "descending"

        }

    Else {

        Write-Verbose "Sorting Ascending via Computer Column"

        $comp_ascend = New-Object System.ComponentModel.SortDescription("Computer","Ascending")

        $Listview.Items.SortDescriptions.Add($comp_ascend)

        $Listview.Items.Refresh()

        $compsort = "ascending"  

        }  

    })

 

By connecting to the necessary controls, I am able to add an event that, when the user clicks the column header under the Computer column, will sort the columns by either ascending or descending order based on its previous state. The Refresh() method ensures that the sorted data is updated in the Listview so that the user can see the sorted data.

 

Conclusion

As you can see, I did run into some issues that could be considered show stoppers, but after some research and trial and error, I was able to come up with workable solutions to each issue. There were some other items that were stumping me, but these were my top issues that I felt others may be able to use in their endeavors.

Speaking of anyone working on a UI in Windows PowerShell, if you are on the fence about possibly writing some sort of UI in Windows PowerShell, I say go for it! Whether it is Windows Forms, WPF with or without XAML, or ShowUI, you should give it a shot and see what you can create. It doesn’t matter if it is a small item that is just looking at the processes of a remote system or something much more complex. It is a great opportunity to expand on your knowledge and build something cool in the process.

I hope everyone enjoys my latest project. Feel free to leave feedback on any bugs or features that you might think would be useful for this utility.

 

Boe, thank you for writing an awesome module and for taking the time to share your experiences in developing the project 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

 

 

Automate Facebook with a Free PowerShell Module

$
0
0

Summary: Guest Blogger Jon Newman shows how to automate Facebook with a free Windows PowerShell module.

 

Microsoft Scripting Guy Ed Wilson here. It’s Guest Blogger Week! Today, we have Jon Newman from Microsoft who is going to talk to us about his new Windows PowerShell Facebook module. Take it away, Jon.

 

Thanks to Ed Wilson for giving me space on the Hey, Scripting Guy! Blog!

My name is Jon Newman, and I’m an old hand at Windows PowerShell. I was on the Windows PowerShell 1.0 team, with Bruce and Jim and all the rest. My primary responsibility was for pipeline execution and error handling. Someday I should write a blog entry on FullyQualifiedErrorId, but that is for another time. I started at Microsoft in September 1989, and I have been writing management UI for Windows Server for 20 years. The Facebook project doesn’t actually have anything to do with Microsoft though.

A few years ago, I joined the board of Hillel at the University of Washington. Like many other businesses and organizations, they use Facebook extensively to interact with program participants and backers. I was struck, however, by the lack of the sort of automation commonly used to manage other mission-critical IT. I looked around and did not find anything usable in this space, although there are a couple other mostly obsolete Windows PowerShell integration projects. So I decided to jump in. This is my first open source project and I am sure I have a lot to learn.

First, my Facebook IT Manifesto motivating this project: Facebook is today mission-critical to many organizations’ customer relationships, as significant as are email and web presence. Therefore, such organizations should manage their Facebook presence with the same IT rigor as other mission-critical technologies such as email and websites. I assert in particular that:

  1. An organization’s Facebook presence must be under ultimate administrative control of the organization itself, and not any particular employees. Customer relationships within Facebook must carry forward as employees leave, enter, and change roles within the organization.
  2. An organization must be able to manage its customer relationships within Facebook members in conjunction with other means of contacting the same customers.
  3. An organization must be able to automate routine management of their Facebook presence.
  4. An organization must document its formal Facebook processes with the same rigor as its formal processes for other elements of IT.
  5. Where social networking technologies other than Facebook have similar importance to an organization, the same applies to them as well.

As a partial answer to need #3, I offer the Facebook PowerShell Module. This technology (still in alpha at this writing) enables organizations to automate and integrate Facebook using Windows PowerShell.

Let us dive right into some examples, and talk theory later:

PS C:\Windows\system32> get-fbobjectdata

email        : jonn_msft_testuser@hotmail.com

id           : 100002097205662

last_name    : NewmanTest

name         : JonTest NewmanTest

first_name   : JonTest

languages    : {@{id=106059522759137; name=English}, @{id=105673612800327; name

               =German}}

link         : http://www.facebook.com/profile.php?id=100002097205662

locale       : en_US

timezone     : -7

updated_time : 2011-05-29T15:04:10+0000

birthday     : 06/30/1964

gender       : male

OK, so I’ve skipped a step. You first need to run New-FBConnection from the Windows PowerShell ISE, which grants permission to access your Facebook account. If you forget, the module will talk you through it, but if you need the details, go to this webpage.

If you do not specify which Facebook object you want to know about, the default is you. This is my test account and the default list of fields.

PS C:\Windows\system32> get-fbobjectdata -fields languages

Id                                 languages

100002097205662          {@{id=106059522759137; name=English}...

If you want fields other than the default fields, see Facebook’s documentation. In this case, we requested the nondefault information “languages.”

PS C:\Windows\system32> get-fbfriends

Id                                 name

<number>                    Matthew <name>

<number>                    Mikael <name>

<number>                    Naomi <name>

<number>                    Jon <name>

Facebook data is basically a gigantic relational database. You are an object, you can have lots of fields, and the fields can be references to other objects. In this case, you are asking for the list of objects in your Friends list.

PS C:\Windows\system32> get-fbfriends -fields gender | group gender

Count                Name                           Group

3                      male                             {@{id=604652494; gender=male}, @{id=67396290

1                      female                          {@{id=1022311059; gender=female}}

Because this is Windows PowerShell, you get all the goodness of its standard commands such as Where, Select, and Group.

function Backup-FBFriends

{

    $timeString = $(get-date) $dateTime.ToString("yyyy-MM-dd-HH-mm-ss")

    $filename = "c:\temp\friends.$timeString.csv"

    write-verbose "Backing up friends list to $filename"

    $friends = Get-FBFriends

    $userdata = $friends | Get-FBObjectData

    $userdata | Export-Csv $filename

}

The preceding code is a more sophisticated example (part of FacebookExamples.ps1 that comes with FacebookPSModule). This script automatically backs up your Friends list to a CSV file in c:\temp. It’s the sort of thing you could run in a scheduled task, so you can monitor changes over time. 

The final example is the Write-EventAttendeeCsv script (also in FacebookExamples.ps1). I have this running in a scheduled task at the client site. It’s a little too long to reproduce here, but it’s a good example of what you can accomplish with FacebookPSModule. The script does the following:

  1. Gets the list of all events associated with a particular group.
  2. Filters to those events in the upcoming week.
  3. Extracts the RSVP list for those events.
  4. Writes an attendee list file for each event to the temp directory.

This way, program staff can print out an attendee list before the event, based on RSVPs for the Facebook event. 

So, is FacebookPSModule right for you? In theory, you could use this to automate anything you can do using the Facebook website. The reality isn’t quite so simple.

First, there are a few things that Facebook doesn’t let you do:

  • You can’t read the list of your friends’ friends. I think this is on purpose—Facebook just decided that allowing Facebook applications to do this is this too much of a privacy risk. As far as Facebook is concerned, FacebookPSModule is a Facebook application, even when you’re just accessing your own data.
  • You can’t write status messages that link pages such as @Microsoft. I think this is just an oversight in the API.
  • You can link photos, but you can’t link directly to other users’ photos in Facebook’s CDN (for example, photos in other users’ albums). This is permitted from the website but not from the API.

I will probably discover more issues as I fill out and explore the module’s capabilities.

More importantly, users should be aware that the Facebook API churns fast, especially the authentication stack. Their interfaces from two years ago are already deprecated and largely broken. You’ll have to keep an eye on your automation to make sure it is working properly.

FacebookPSModule stores your Facebook access token by default (encrypted using DPAPI via ConvertFrom-SecureString) in your user profile, which is why subsequent commands including scheduled tasks can read it. You should think about security for your user profile, because anyone with access to the access token can perform Facebook tasks against your account. In the worst-case scenario:

  1. On Facebook.com, click Account, and then click Privacy Settings.
  2. Under Apps and Websites, click Edit your settings. Under Apps you use, click Edit Settings.
  3. For Test Application jonn_msft (or whichever AppId you used), click Edit. On the next page, click Remove app to revoke permissions for the application, including its access token.

Finally, most important of all, FacebookPSModule is (as of August 15, 2011) only in alpha. There are plenty of known issues, including issues with large data sets. I will probably change the cmdlet names and signatures as I learn more about this space.

FacebookPSModule is really a thin layer over the Facebook C# SDK, which is itself a thin layer over the Facebook Graph API. When you run New-FBConnection, you are requesting an OAuth 2.0 access token for an application I registered. The application doesn’t really exist, and you can use another application ID if you like; you just have to specify an application ID to get the access token.

I could share some war stories about developing this, in particular about supporting .NET Framework 4.0 and converting .NET Dynamic Objects to Windows PowerShell. If there is enough interest, I will write a follow-up blog entry.

What I need from you are more real-world scenarios. Tell me what you want to do, and I can make sure FacebookPSModule does it well. If you want to help with the development, please do! http://facebookpsmodule.codeplex.com/discussions is the place to talk.

Happy Facebooking!

 

Thank you, Jon, for writing a really cool Facebook module, and for taking the time to share your experience with us.

Join us tomorrow for day 2 of Guest Blogger Week when Trevor Sullivan will be our guest.

 

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

 

 

Monitor and Respond to Windows Power Events with PowerShell

$
0
0

Summary: Guest Blogger Trevor Sullivan shows how to monitor and to respond to Windows Power events using Windows PowerShell.

 

Microsoft Scripting Guy Ed Wilson here. Today’s guest blogger is Trevor Sullivan, and he has a fascinating article about responding to power management events. First, a little bit about Trevor. 

Photo of Trevor Sullivan

Trevor Sullivan is a passionate, experienced, and Microsoft-certified IT pro with more than seven years in the industry. Although he is interested in nearly all Microsoft technologies, his primary specialties include:

  • Design, implementation, and troubleshooting of Microsoft System Center Configuration Manager 2007.
  • Automation using Windows PowerShell, Windows Management Instrumentation (WMI), Active Directory Services Interface (ADSI), and the Microsoft .NET Framework.

Trevor is a Microsoft-recognized Community Contributor (MCC) and is active in several online communities:

One of his major achievements is the development and public release of an open-source Windows PowerShell module called PowerEvents. During his personal time, he enjoys studying theology, spending time with his girlfriend, being outdoors, shooting photos, playing video games, testing software, and learning about all sorts of new things.

Take it away, Trevor!

 

Introduction

Oftentimes, people want to be able to respond to events automatically on their computers: “When <X> happens, I want <Y> to happen in response.” An example of this might be: “If SomeProcess.exe exceeds 50 percent processor utilization for 60 seconds, kill it.” Usually this would require some custom systems monitoring software, but what if I told you that your computer had this functionality built into it already? That’s right, little known to most people is the WMI background service, which provides a robust eventing and event response model.

Although power management hasn’t always been a highlight of the Microsoft Windows operating system (OS), it’s certainly come a long way in Windows 7 and is now quite robust. Sleeping and hibernating in Windows 7 are both quite fast, and resuming from both states is likewise very quick. But what if you want to do something when your computer wakes up? Though this may not be a terribly common scenario, sometimes people have the need to subscribe to this event and perform an action in response to it.

In the remainder of this article, we will take a look at how to subscribe for system-level power management events, and how to respond to them. We will be working with the following technologies:


WMI Power Management events

Microsoft has built a robust power management provider into Windows 7, and thankfully for us, they have exposed its functionality via the WMI service. WMI provides a standards-based interface in the operating system and applications that extend it. Although WMI has suffered from reliability and performance problems in the past—primarily on Windows XP—modern-day hardware combined with the newest Windows 7 operating system is quite reliable. Microsoft has resolved a lot of WMI bugs such that it is a very dependable service.


Power Management WMI Provider

All WMI providers (extensions to WMI) are registered in a particular WMI namespace under the __Win32Provider class. We can ensure that the Windows Power Management provider is registered by running this WMI query from Windows PowerShell:

@(Get-WmiObject -Namespace root\cimv2 -Query "select * from __Win32Provider where Name = 'MS_Power_Management_Event_Provider'").Count

If this query returns a result of “1,” we know that the provider is registered.


Win32_PowerManagementEvent class

The power management provider exposes a single WMI class called Win32_PowerManagementEvent, which is an extrinsic event class. Extrinsic event classes differ from intrinsic event classes in that the events they provide come from an external provider (the Power Management WMI provider), rather than them representing a change to a WMI object.

The Win32_PowerManagementEvent class only has one property that we really care about, which is the EventType property. The possible values for this property are:

Value

Meaning

4

Entering suspend

7

Resume from suspend

10

Power status change

11

OEM event

18

Resume automatic

As you might gather, we are interested in events that have a value of "7," which represents a system resume.


Example Scenario

In this example scenario, we are going to take a look at how to restart a Windows service when the system resumes. Specifically, I recently noticed that the PS3 Media Server software has an issue with power management in that it does not listen for connections upon system resume from Standby/Hibernate. This has reportedly been a problem with Windows 7 Ultimate Edition 64-bit.

To work around this issue, we’ll look at how to restart the PS3 Media Server service each time the computer resumes from a low power state.


Using PowerEvents

The use of the PowerEvents model follows a three-step process:

  1. Create WMI event filter using WQL.
  2. Create an event consumer (response to the event occurrence).
  3. Create a WMI binding between the event filter and the event consumer.

We will cover these three steps individually below.


WQL event filter

First, we need to build an WMI event filter using the WMI Query Language (WQL). WQL is similar to Structured Query Language (SQL), but is much more limited in scope. WQL does not support INSERT, UPDATE, or DELETE statements; it only supports SELECT queries. We’re going to follow the event query template:

select * from <WmiClass> WITHIN <PollInterval> where <Criteria>

In this case, we’re going to use the following values for our event query:

  • WmiClass = Win32_PowerManagementEvent
  • PollInterval = 5
  • Criteria = "EventType = 7"

Our resulting query will look like this:

select * from Win32_PowerManagementEvent WITHIN 5 where EventType = 7

The command we’ll use to create our WMI event filter using the PowerEvents module for Windows PowerShell looks like this:

$Filter = New-WmiEventFilter -Name SystemResumed -Query "select * from Win32_PowerManagementEvent where EventType = 7"

We store the filter object in a Windows PowerShell variable for later use in the event binding.

More information about WMI event queries can be found in the PowerEvents documentation. The PDF is located in the Documentation folder of the PowerEvents download. This document includes information about how to test your WMI event query using the wbemtest.exe utility, before creating the permanent event registration to reduce troubleshooting hassle.


Event consumer

Now that we have created (and tested, right?) the event filter, we need to create an event consumer. In this example, we’ll use a Windows PowerShell script to stop and start the PS3 Media Server service (short name: PS3 Media Server). The script itself contains this code:

$ServiceName = $args[0]

Add-Content -Path 'c:\Restart Service.log' -Value "Service name is: $ServiceName"

$Service = @(Get-WmiObject -Namespace root\cimv2 -Class Win32_Service -Filter "Name = '$ServiceName'")

Add-Content -Path 'C:\Restart Service.log' -Value "Found $($Service.Count) instances of '$ServiceName' service"

$Result = $Service[0].StopService()

Add-Content -Path 'c:\Restart Service.log' -Value "Stopped service with result: $($Result.ReturnValue)"

Start-Sleep 4

$Result = $Service[0].StartService()

Add-Content -Path 'c:\Restart Service.log' -Value "Started service with result: $($Result.ReturnValue)"

Add-Content -Path 'c:\Restart Service.log' -Value "Exiting restart service script"

Save this code in a file called c:\windows\temp\Restart Windows Service.ps1.

To create the event consumer object in WMI, we’ll use the following command:

$Consumer = New-WmiEventConsumer -Verbose -Name SystemResumedRestartService -ConsumerType CommandLine -CommandLineTemplate "powershell.exe -command `". '$($env:WinDir)\temp\Restart Windows Service.ps1' 'PS3 Media Server'`""

This command creates a command-line consumer — that is to say, we want to call a command-line utility in response to the event that occurs. We give it a friendly name so that we know what it runs in response to, and what it does in response to the event: SystemResumedRestartService. Then we use the CommandLineTemplate parameter to specify the command line we want to execute in response to the event. In this case, we’re calling Windows PowerShell and passing it our script file via the -command switch along with an argument to the script file. We use script arguments to make our script dynamic. All we have to do to change the service that gets restarted is change the parameter that we’re passing to it. We don’t have to touch the script itself at all.

Important   Make sure you have configured your Windows PowerShell execution policy to allow execution of script files; otherwise, the event consumer will fail. Run Windows PowerShell with your administrative token and use this command: Set-ExecutionPolicy Unrestricted.


WMI event binding

Finally, now that we have created our event filter and event consumer, all we have to do to initiate the flow of events is bind them together. We’ve got the filter and consumer stored in variables called $Filter and $Consumer, so all we have to do is call this command:

New-WmiFilterToConsumerBinding -Filter $Filter -Consumer $Consumer


Testing

And that’s it! We’re done. Now that all the WMI objects have been created, all we have to do is suspend and resume our workstation to test the process. After the system is restarted, we should see a c:\Restart Service.log file created. Check this log to ensure that the service you specified in the event consumer command-line was properly stopped and started.


Conclusion

This article has demonstrated the use of the PowerEvents module for Windows PowerShell to create an event listener (filter)/responder (consumer) for wake-from-low-power-state events. Although this particular example restarts a Windows service in response to such an event, you can use your creativity to come up with other tasks you might need to fire off at the same occurrence.

Note   For more information about working with permanent and temporary WMI events, see  this collection of Hey, Scripting Guy! Blog posts. This collection includes a post about using VBScript to create permanent WMI events. This post is important because it discusses the basics of permanent WMI events. Next, I talk about using Windows PowerShell to monitor and to respond to events on the server. This post continues the discussion about permanent WMI events. This is followed by the first of two articles from Trevor that talk about his Windows PowerShell module to work with WMI permanent events. The second Trevor article in the series talks about using the Windows PowerShell WMI event module to quickly monitor events.

 

Thanks Trevor for an interesting article, and for writing your Windows PowerShell module for working with WMI Permanent Events.

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

 

 

BATCHman Uses PowerShell to Battle the Dread Madame CAT File

$
0
0

Summary: In today’s episode, intrepid BATCHman uses Windows PowerShell to battle the dreaded Madame CAT file—and the fur really flies.

 

Microsoft Scripting Guy Ed Wilson here. Windows PowerShell MVP Sean Kearney returns today for part 2 in his BATCHman series.

BATCHman and Cmdlet graphic

 

Deep in the most unforgiving lines of code, a new superhero emerges to bind the illogical, bring peace to the unfathomable binary things in the night, and seek justice where none can be found.

It’s your friend and mine, BATCHman!

….and his sidekick, Boy Blunder Cmdlet.

 

In Today’s Episode: BATCHman encounters the Dreaded Madame CAT file

Oh, no! The JobsBlog Team has been victimized by Madame CAT file! Madame CAT file loves to do nothing but rename files and place CAT all over the place. Today, she has attacked the folder structure of Microsoft Office leaving the JobsBlog Team helpless!

“Oh, how will we ever be able to review the resumes now? No Outlook! No Word! All of these resumes trapped! Resources untouched! Oh, where is BATCHman? Help us!”

Flying out of nowhere in a flurry of SMS packets and Gorilla Glass, and arriving with a classic “TADA” wav file from Windows 3.1, it’s..it’s…

The truly dynamic (as opposed to static) BATCHman and Cmdlet!

“BATCHman! Thank goodness you’re here! We have millions of resumes to go through and Madame CAT file has rendered us helpless! Our copies of Microsoft Office are covered with nothing but the word “CAT.”

“Holy kitty litter, BATCHman!” bursts out Cmdlet. “What can we do?”

BATCHman quickly assesses the situation. There are two options he foresees.

“Well, Cmdlet, we could just do a System Restore to before Madame CAT file released her claws, but that will take time on all these computers. We need to restore the entire Jobs Blog to action quickly! If we aren’t careful, some fool might post over 8,000 resumes to the system and overload the team! It’s happened once before, and we just can’t take a chance!”

BATCHman thinks for a moment. “You know, I think we can take a shot at undoing this. Let’s see just how much damage that villain really did. I know Windows stores additional properties about certain key files, such as DLLs and EXEs, in the file itself.”

Cmdlet looks up. “Really? Zowie! How do we pull something like that up?”

BATCHman looks at an afffected file:

GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE

“As we can see, Cmdlet, the file name has been completely tampered with, but if we run this against the Get-Member cmdlet, we can see there are additional properties available to us:

GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE | GET-MEMBER

“I can see we have a property called VersionInfo. My scripting sense is tingling here. If we were to access this with a Select-Object cmdlet and see what it looks like…”

 Image of VersionInfo property

“Holy Harry Houdini, BATCHman! There’s the file name hidden in the Filename properties!”

“Yes, Boy Blunder, but we’ll have to dig a bit further. So let’s access the property directly to find the name of the property it can be referenced by. We could run the Get-Member cmdlet against it like this.”

(GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE).VersionInfo | GET-MEMBER

“But let’s see if the information is easily accessible. Windows PowerShell has a tendency to try and make this easier.”

(GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE).VersionInfo

Cmdlet just about dropped a process when he saw the results. “BATCHman! So if I were to type the Filename property…”

(GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE).VersionInfo.Filename

His heart sank. “Oh, no! It’s the same! AIHGAIHGIAHGHIHAIGHIH!!!”

“Calm down, little Cmdlet. There are other properties. Sometimes just piping the output to the Format-List cmdlet will reveal them in a more useful manner. Remember, we’re looking at the default output in Windows PowerShell.”

(GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE).VersionInfo | FORMAT-LIST

“So although you are correct in that the Filename shows the current file name, there is actually a property called OriginalFileName, which has been untouched by our Evildoer, and we can access it like this.”

(GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE).VersionInfo.OriginalFileName

“BATCHman! Do you mean we could just check the properties of the .dll files and .exe files and possibly just rename them to the original? Even Madame CAT file made the name unrecognizable?”

Patting Cmdlet on the back he smiled. “That’s exactly what I mean!”

Then Cmdlet sighed. “BATCHman, that’s going to be a horribly complicated script! To pull up all those .dll and .exe files, analyze them, rename them, examine them…,” he rambled on.

“You forget, my young friend, that with Windows PowerShell, to pull such a list we simply need to specify the file extensions we want to include and recurse like this.”

GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\ –include *.exe, *.dll –recurse

“Yes I know! But how can we just rename them? To properly do that, we need the file path, the drive letter!” Cmdlet began to sob “WE’RE DOOOOOMED! DOOMED! DOOOOOOOOMED!”

“We could just run a Foreach, pull up the properties, and Rename the bad file name with itself like this, Cmdlet,” he stated calmly.

GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\ –include *.exe, *.dll –recurse | Foreach { Rename-item $_.Fullname –newname $_.OriginalFilename }

“B-b-but, BATCHman! There’s more than 200 files we could be affecting! What if we ruin them? What if we do something horrendously bad? What if your code destroys everything?”

“Funny you should mention the ‘what if.” I was thinking that very same thing. That’s why Windows PowerShell has a parameter called –whatif that is generally provided with any cmdlet that could make a change to the system. We just add it on like this and execute the code.”

GET-CHILDITEM C:\Program Files\Microsoft Office\Office14\ –include *.exe, *.dll –recurse | Foreach { Rename-item $_.Fullname –newname $_.OriginalFilename -whatif}

Cmdlet watched in amazement as each line was executed in the following fashion:

What if: Performing operation "Rename File" on Target "Item: C:\Program Files\Microsoft Office\Office14\MCATPUB.EXE Destination: C:\Program Files\Microsoft Office\Office14\MSPUB.EXE".
What if: Performing operation "Rename File" on Target "Item: C:\Program Files\Microsoft Office\Office14\MSCAT32.EXE Destination: C:\Program Files\Microsoft Office\Office14\MSQRY32.EXE".
What if: Performing operation "Rename File" on Target "Item: C:\Program Files\Microsoft Office\Office14\MSCATEDIT.DLL Destination: C:\Program Files\Microsoft Office\Office14\MSRTEDIT.DLL".
What if: Performing operation "Rename File" on Target "Item: C:\Program Files\Microsoft Office\Office14\CATWORDMEOW.EXE Destination: C:\Program Files\Microsoft Office\Office14\WINWORD.EXE".

“You see? With Windows PowerShell, we have a built-in way to test without breaking anything. As we can see, it appears this script should work. I’ll remove the –whatif parameter and execute as normal.”

Moments later, a system was recovered. All of the CAT litter was gone!

“Now quickly, Cmdlet! Take this USB key, run Windows PowerShell with administrative rights, and execute this code on each affected machine!”

In a matter of minutes the entire team was restored to productivity!

“Now keep in mind, Cmdlet, that this was a break/fix—a good one but a brake/fix nonetheless. We should speak to IT about having System Restore run on these machines later. But for the moment, we’re good!”

“Oh, BATCHman! How can we ever thank you?” burst out the JobsBlog Team.

“Easy! Always trust in Windows PowerShell and try not to let any more CATs in the office with local administrative access. BATCHman…AWAY!”

 

Thank you, Sean. Everyone come back tomorrow for part 3. Same BATCHtime, same BATCHchannel.

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


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>