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

PowerShell and Configuration Manager 2012 R2–Part 4

$
0
0

Summary: Use the Configuration Manager cmdlets to work with driver packages.

Hey, Scripting Guy! Question Hey, Scripting Guy!

My current job involves creating packages in Configuration Manager 2012. Is there some way to automate the process? It’s not that it’s actually difficult, but it is a very repeatable process. I thought to myself, “Repeatable? This sounds like a job for PowerShell!" Help out a friend and tell me there are cmdlets to do this.

—KB

Hey, Scripting Guy! Answer Hello KB,

Honorary Scripting Guy, Sean Kearney, is here today with relief for you. Yes, there are cmdlets for that!

If you’ve been following along this week, you have seen that we can manage collections and update distribution points by using the Configuration Manager cmdlets.

   Note  This is Part 4 of a five-part series. Also see:

Yup, there’s a cmdlet to meet your needs, too! If you read the previous posts in this series, you can almost guess how to identify the potential cmdlets for working with driver packages. We simply need to execute:

Get-Command –Module ConfigurationMananger *Package*

Image of command output

There is a decent set to handle almost all of the package needs that I can personally see. Cmdlets to handle driver and software packages are ready to go.

I can do something as simple as list all the drivers presently within Configuration Manager by using Get-CmDriver, or I can get a list of driver packages with Get-CmDriverPackage.

These cmdlets need their formatting cleaned up to pull down data. There is way too much information on the screen. To save you some trouble, I’ll get the list of objects I find useful to almost mimic the output in the console:

Get-CmDriver | Select-Object LocalizedDisplayName,DriverProvider,DriverClass,DriverDate,DriverVersion

…and for driver packages:

Get-CmDriverPackage | Select-Object Name,Version,PackageID

Let’s look at something pretty typical—creating a driver package.

You’ve created a driver package in Configuration Manager at least once or twice, right? To create a driver package with Windows PowerShell, you’re going to need three cmdlets:

  • Import-CMDriver    Imports a single driver into Configuration Manager
  • New-CMDriverPackage   Creates a driver package (if needed) in Configuration Manager
  • Add-CMDrivertoDriverPackage   Adds a tagged driver into a Configuration Manager package

If you don’t already have a package for your drivers, you can create one quite easily. You only need two things: a name to give to the package and a UNC path name to store the package contents.

$PackageName=’My Sample Driver Package’

$PackageSource=’\\ContosoSCCM\DriverPackages\SampleDriverPackage’

New-CMDriverPackage –name $Packagename –path $PackageSource

If you need to import a single driver, you would use the following command, which has the driver path supplied in the object named $DriverPath:

$DriverPath='\\sccm2012r2\sourcefiles\driver\NetworkCard\Drivers\08MVF\release\MxG2wDO.inf'

Import-CMDriver –UncFileLocation $DriverPath -ImportDuplicateDriverOption OverwriteCategory -EnableAndAllowInstall $True

For some reason, although the object displays properly on the screen, it does not capture properly. However, if we pipe it through Select-Object and grab all available objects, we can capture it. Here is a great blog post from Operating System Deployment Couture that explains this: SCCM 2012 R2: Make Import-CMDriver cmdlet work for you.

We will now capture the output from this cmdlet to leverage it as we add it to the driver package. To add our newly imported driver to a driver package, we grab the property LocalizedDisplayName from the results as we import each one. We need this information and the name of the driver package.

$DriverName=(Import-CMDriver –UncFileLocation $DriverPath -ImportDuplicateDriverOption OverwriteCategory -EnableAndAllowInstall $True | Select-Object *).LocalizedDisplayName

We now supply this information to Add-CMDrivertoDriverPackage:

Add-CmDrivertoDriverPackage –DriverName $DriverName –PackageName $PackageName

There you go! We have now added a driver to a new package by using PowerShell!

A limit to Import-CMDriver is that it imports a single driver into Configuration Manager. We all know that we can target a folder and discover all the drivers within in the GUI.

If we’d like this same capability in Windows PowerShell, we can use Get-ChildItem. We can target only files with the .inf extension while recursing the folder structure:

Get-Childitem ‘\\ContosoSCCM\SccmShare\Drivers\SampleDriver’ –recurse –include *.inf

This will get stored away as an object:

$DriverList=Get-Childitem ‘\\ContosoSCCM\SccmShare\Drivers\SampleDriver’ –recurse –include *.inf

With these two simple commands, we can do exactly what you typically do in the GUI of Configuration Manager, which is create driver packages and import the contents there.

If we wrote this entire process from package creation to driver imports, and added the packages as a script, it would look like this:

$PackageName=’My Sample Driver Package’

$PackageSource=’\\ContosoSCCM\DriverPackages\SampleDriverPackage’

New-CMDriverPackage –name $Packagename –path $PackageSource

Foreach ($Driver in $Driverlist)

{

$DriverName=(Import-CMDriver –UncFileLocation $DriverPath -ImportDuplicateDriverOption OverwriteCategory -EnableAndAllowInstall $True | Select-Object *).LocalizedDisplayName

Add-CmDrivertoDriverPackage –DriverName $DriverName –DriverPackageName $PackageName

}

Of course, we could improve this script by adding a parameter for the package name, the source, and the destination. You could even add what you learned yesterday about updating distribution points. But this is a good start.

If we decided we need to remove a particular driver from a package, we supply the same information to the Remove-CmDriverfromDriverPackage cmdlet:

Remove-CmDriverfromDriverPackage –drivername $DriverName –driverpackagename $PackageName

If you make a mistake, you can remove a driver by using Remove-CmDriver. The following command would prompt you, then completely delete the driver:

Remove-CmDriver $Name

With these cmdlets, you could make your regular process of managing drivers in Configuration Manager so much easier.

KB, that is all there is for working with drivers today, but pop in tomorrow when we use PowerShell to create applications in Configuration Manager!

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to them at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, always remember that with great PowerShell comes great responsibility.

Sean Kearney, Honorary Scripting Guy and Cloud and Datacenter Management MVP


PowerTip: Use PowerShell to Identify Network Drivers in Configuration Manager

$
0
0

Summary: Use the Configuration Manager cmdlets in Windows PowerShell to identify all network drivers.

Hey, Scripting Guy! Question I deal with drivers detection issues in Windows PE because we have too many network card drivers.
           How can I use Windows PowerShell to quickly show all of the network drivers in Configuration Manager?

Hey, Scripting Guy! Answer Use the Get-CmDriver cmdlet and filter on the DriverClass property, for example:

$DriverClass=’Net’

Get-CmDriver | Where { $_.DriverClass –match $DriverClass }

You can pipe this object to Select-Object to choose the information most relevant to you.

PowerShell and Configuration Manager 2012 R2–Part 5

$
0
0

Summary: Use the Configuration Manager cmdlets to create an application.

Oh, Scripting Guy! I want to get home at a decent hour today, but I need to bring this massive list of applications into Configuration Manager. I heard the Configuration Manager cmdlets can do this, but I need some help. So…er…*Cough*...HELP!

—TR

Hello TR,

Honorary Scripting Guy, Sean Kearney, is here today to help you. I know vividly what you’re talking about. I’ve had to do the same thing.

   Note  This is the final post in a five-part series. Also see:

Configuration Manager is a great environment for package and application deployment and for managing the enterprise. The trick is getting the information in there for deployment.

So first, let’s see the pile of cmdlets you have to work with:

Get-Command –module configurationmanager *Application*

Image of command output

This list is quite impressive. There appears to be almost nothing we can’t do with applications. If you look at yesterday's post, there is an equally strong pile for creating software packages.

After looking at this pile, my thought is, "Why would anyone repeatedly use the GUI?"

Let's script it all!

The first part I think is pretty cool is easily getting a list of all applications or software packages in Configuration Manager in one line. We can use this to show all the applications that are available:

Get-CmApplication | Select-Object LocalizedDisplayName

Or to see all of the available packages, use:

Get-CmApplication | Select-Object Name

To create a new software package requires two cmdlets, New-CmPackage and New-CmProgram.

New-CmPackage creates the base package. Much like the GUI, there is a lot of information you can provide, but only certain bits are actually required.

If you want to create a basic package with the source files on a server file share that targets a user (pretty typical), you’d execute New-CmPackage in this fashion:

$SourcePath=’ContosoSCCM\SccmShare\HSGProgram’

$PackageName=’HSG Cool Program’

$PackageDescription=’Some amazing App written by the PowerShell Community’

$Manufacturer=’Zaphod Beeblebrox’

$Language=’GargleBlaster’

$Version=’1.2’

$CMPackage=New-CmPackage –Name $Packagename –Description $PackageDescription –Manufacturer $Manufacturer –Language $Language –Version $Version –Path $SourcePath

We grab the output so we can get the PackageID for our next step, which is to add a program to our package containing source files.

If you were to type $CMPackage.PackageID, you’d see a familiar piece of information on your screen! The very ID we normally see in the Configuration Manager console.

We can now run New-CmProgram and create a launch for this application. We’ll presume the application that launches it is called setup.exe, and we’ll give it an appropriate name:

$ProgramCommand=’setup.exe’

$ProgramName=’HSG program’

$DiskSpace=’10’

$DiskspaceUnit=’GB’

$Runtype=’Normal’

$RunMode=’RunWithAdministrativeRights’

$CMProgram=New-CMProgram –Commandline $ProgramCommand –PackageID $CMPackage.PackageID –StandardProgramName $ProgramName –DiskSpaceRequirements $DiskSpace –Diskspaceunit $DiskSpaceUnit –RunType $RunType –RunMode $RunMode

With Configuration Manager 2012, if you prefer to see the packages show up in the software center on the client, you need to clear the Suppress program notifications option. You could go to the GUI for this, but we can add the Set-CmProgram cmdlet to the script in question. We supply the package ID and the name of the program as the key parameters:

Set-CmProgram –PackageID $CMPackage.PackageID –ProgramName $CmProgram.ProgramName

Applications are not that much different. There are two cmdlets to work with. The biggest difference with applications is that you need to add your detection type to confirm the installation.

The first part is to create the entry in the application list for our custom app. Most of the following variables reflect their purpose:

$AppName=’HSG App’

$AppDescription=’Really Cool HSG App’

$AppPublisher=’Hey Scripting Guys’

$AppVersion=’1.1’

$AppReleaseDate=’11/20/2015’

$AutoInstall=$True

$AppOwner=’MrEd’

$CMApp=New-CmApplication –name $AppName –Description $AppDescription –Publisher $AppPublisher –SoftwareVersion $AppVersion –ReleaseDate $AppReleaseDate –Autoinstall:$True

The next piece is to add our deployment. For this example, we’re going to work with a simple MSI file:

$SourcePath=’ContosoSCCM\SccmShare\HSGProgram\HSGapp.Msi’

Add-CMDeploymentType -MsiInstaller -ApplicationName $AppName –AutoIdentifyFromIntallationFile -InstallationFileLocation $SourcePath -ForceForUnknownPublisher $True

This provides us with a basic application with automatic detection for MSI.

I could probably spend almost another week talking about these cmdlets. I’ve not even touched on adding the packages or applications to distribution groups. We could even delve into writing the custom scripts needed to detect non-MSI applications.

I hope you have discovered that you can get some basic automation done with your Configuration Manager environment—perhaps enough to get you home a bit earlier!

TR, that is all there is to using the Configuration Manager cmdlets for this week.

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to them at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, always remember that with great PowerShell comes great responsibility.

Sean Kearney, Honorary Scripting Guy and Cloud and Datacenter Management MVP 

PowerTip: Use PowerShell to Show Deployment Types for Application

$
0
0

Summary: Use the Configuration Manager cmdlets to identify all deployment types for an application.

Hey, Scripting Guy! Question How can I use Windows PowerShell to pull up a list of all deployment types for one of my applications?

Hey, Scripting Guy! Answer Use the Get-CmDeploymentType cmdlet and supply the application name in question, for example:

Get-CmDeploymentType –ApplicationName ‘HSG Application’ | Select-Object LocalizedDisplayName

Weekend Scripter: The Case of the Disappearing PowerShell Console

$
0
0

Summary: Ed Wilson, Microsoft Scripting Guy, talks about fixing a problem where his Windows PowerShell console disappears.

Microsoft Scripting Guy, Ed Wilson, is here. So there I was, on a two week trip, out of time, with only my laptop, and I needed to use Windows PowerShell. Most of the time, I use the Windows PowerShell console, but I do occasionally use the Windows PowerShell ISE. At home, I have a nice-sized second monitor that is attached to my laptop—but I do not—actually the truth is, I rarely travel with my second monitor. I have been known to take it with me on a road trip.

So I click to open the Windows PowerShell console and…nothing. Hmm, I try it again. This time I noticed that the Windows PowerShell console had in fact opened—it was just missing. As shown here, I noticed a white underline beneath the Windows PowerShell console icon I have on my tool bar in Windows 10.

Image of tool bar

I pressed CTRL + Shift + Esc to bring up Task Manager. I saw that Windows PowerShell was in fact running, but I could not see it:

Image of menu

I ended my Windows PowerShell session, closed the Task Manager, and tried again. I confirmed that Windows PowerShell was loading, but it was not visible. Bummer.

Finding a visible Windows PowerShell console

Then I had an idea…

I can use the Windows PowerShell ISE, but I still wanted the Windows PowerShell console. Guess what? There is an icon for the Windows PowerShell console in the tool bar of the Windows PowerShell ISE. I clicked that, and it worked. The icon is shown here:

Image of menu

And that is what I did for two weeks. I clicked the little Windows PowerShell icon on the Windows PowerShell ISE tool bar, and then I had an instance of the Windows PowerShell console I could use. It was not too bad because I often have both the ISE and the console open anyway, but I used to always open the console first—so this was a learning opportunity.

One day, I searched through the registry hoping I could find something that was storing the screen location (usually in pixels in the x and y coordinates). I was hoping to see how it’s opening window position was set, but alas, I did not find it. It might be there, but I could not find it in the 15 minutes I devoted to the task.

Fixing the problem

Eventually I decided to email our internal Windows PowerShell discussion group to see if anyone else had experienced this problem, and if anyone had come up with a solution. After I sent the email, I quickly saw several people replying with +1, which in Microsoft email speak means that they are also having the same problem. Within 15 minutes, someone from our support group replied that I should look at the properties of my Windows PowerShell console window. Remember, that I can get to one via the Windows PowerShell ISE icon.

To find the Windows PowerShell console properties, I click the Windows PowerShell icon in the upper left corner of the Windows PowerShell console window, and then I select Properties from the action menu:

Image of menu

I select the Let System position Window check box on the Layout tab:

Image of menu

He also told me something else that I didn’t know: If the Windows PowerShell console opens off screen, I can click the Windows PowerShell icon on the tool bar to get focus. Then I can press Alt + Spacebar to open the context menu, and press M to enable me to move the Windows PowerShell console via the arrow keys. Cool.

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

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Find Free Commands in PowerShell 5

$
0
0

Summary: Learn how to find free commands in Windows PowerShell 5.0.

Hey, Scripting Guy! Question I would like to customize my command line experience in Windows PowerShell 5.0, but I don’t want to write
           a lot of code. How can I find available commands that are not bound to any keyboard chord sequence?

Hey, Scripting Guy! Answer Use the Get-PSReadlineKeyHandler and look for unbound commands:

(Get-PSReadlineKeyHandler).where({$_.key -eq 'unbound'})

Weekend Scripter: Report on Network Level Authentication

$
0
0

Summary: Boe Prox shows how to use Windows PowerShell to report on Network Level Authentication.

Honorary Scripting Guy and Windows PowerShell MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. I plan to spend this Sunday showing you how you can report on whether your systems are enabled for Network Level Authentication.

By using PowerShell, we can make some amazing scripts and functions that can do a variety of things. Whether we want to build a complex workflow to provision a system, use Desired State Configuration to configure many systems, or even build a UI, we can pretty much do anything we want with Windows PowerShell...within limits.

One of the most simple things we can do (or maybe not simple, depending on the requirement) is report on certain configuration settings of a local or remote system and generate various kinds of reports such as CSV, HTML, or XML. Today we will use WMI to report on a Remote Desktop session setting called Network Authentication Level to see whether it is enabled on a system.

Before we dive into the process of finding and reporting on this, we should have a brief background as to what Network Level Authentication actually is.

Network Level Authentication has been around since Windows 2008, and it provides a way to better secure the Remote Desktop session by requiring a user to authenticate prior to making the connection to a server. Without it, you would be taken directly to the server's logon screen and already have a Remote Desktop session created to support this. Authenticating also forces the server to create several processes to support this and could help with a denial-of-service attack.

Now that we have an idea about what this is and how it can be useful to us, we need to take a look at our systems to see what is actually there and if we need to look at implementing a Group Policy, DSC configuration, or simple script to make this change on those systems. Regardless of our fix action, we need to figure out which systems are not compliant.

Fortunately for us, this setting happens to reside in WMI. This means we can use our approach of choice, Get-CIMInstance or Get-WMIObject to make our connection and pull this information. For this example, I am going to be running with Get-WMIObject.

Before I can actually start writing the code, I need to know what class and namespace I need to pull the data. For the namespace, I am going to use root/cimV2/TerminalServices and the class in question is Win32_TSGeneralSetting. Now that I have this figured out, we can make a quick query to my local system and see how it is configured:

$WMIParams = @{

    Class = 'Win32_TSGeneralSetting'

    Namespace = 'root\CIMV2\TerminalServices'

    Filter = "TerminalName='RDP-Tcp'"

    ErrorAction = 'Stop'

}

Get-WmiObject @WMIParams

Note that I have a WMI filter that is looking for only terminal names that are RDP-Tcp. This saves me some time by filtering on the system rather than pulling back a bunch of unneeded data and sorting that locally.

Of course, this is my local system, but it is more important when you scale this out to remote systems. The result that I get back looks something like the following image, which shows a wealth of information about this particular protocol setting.

Image of command output

We can also tell by UserAuthenticationRequired being set to 1 that Network Level Authentication is enabled on this system.

This is great and it tells us how we can quickly query systems to determine which ones have the setting enabled and which ones do not and need to be addressed. I’m not one to have a single piece of information to report on, so I wrote a function called Get-RDPCertificate which not only pulls this information but also looks at the certificate being used with this configuration and determines if it is a self-signed certificate or a CA-issued certificate. You can download the script from the Script Center Repository: Get-RDPCertificate.

A quick demo of the script against my local machine will again show you the Network Level Authentication setting. It also provides a deeper look into the certificate that is being used to host the session.

Get-RDPCertificate -Computername $env:COMPUTERNAME

Image of command output

Now I have a little more information to present if I am reporting for compliance. I looked to see if Network Level Authentication is enabled, and I also checked to see if I am using a self-signed certificate or one issued by a Certification Authority.

That’s all there is to using PowerShell to report on Network Level Authentication. Hopefully, you can make use of this function and knowledge to begin looking at your environment and reporting on the state of the configuration.

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

Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Find Expiring Certificates by Using PowerShell

$
0
0

Summary: Boe Prox shows how you can find certificates that are expiring.

Hey, Scripting Guy! Question How can I find if I have any certificates on my system that are expiring within 30 days?

Hey, Scripting Guy! Answer Use the ExpiringInDays dynamic parameter when working with the certificate provider, for example:

Get-ChildItem Cert:\LocalMachine\My\ -ExpiringInDays 30 |

Select-Object Thumbprint, Subject, NotAfter


Get Wireless Network SSID and Password with PowerShell

$
0
0

Summary: Jason Walker explores using Windows PowerShell to get the SSID and password for a wireless network.

Ed Wilson, Microsoft Scripting Guy is here. Today I'm turning over the keyboard to Jason Walker. Jason is an Office 365 deployment consultant, who assists customers in the public sector arena. Here's Jason...

Let me start off with a scenario. You are somewhere, anywhere, and a friend of yours asks you for the password to a wireless network. This could be the wireless network at your house or a hotspot. What do you do? In Windows 7, you could easily get that from a viewable preferred wireless network list. In Windows 8, that was removed, and it has not yet returned.

I decided to tackle this problem. I know from experience that netsh.exe will give me this data, but executables return text. To make this useful in PowerShell, I would have to parse the text to retrieve the wanted data and return a usable object. The first thing that comes to mind is to use the Select-String cmdlet. Let’s dig in...

To get the password for a wireless network, the nestsh.exe syntax is as follows:

netsh.exe wlan show profiles name=’Profile Name’ key=clear

Here is the example output from this command:

Image of command output

The only data I’m concerned with are the lines that contain SSID Name and Key Content. At this point, I’m confident that I could easily get the SSID name and password or key content by running the netsh.exe command and storing the output in a variable. Then I could pass that variable to Select-String once to search for SSID Name, and pass the variable a second time to Select-String to search for Key Content

I can then parse each search the same way by using the split method and split on the colon ( : ). This will create an array with two elements. I only want the last element, so I specified the last index in the array with a [-1].

Image of command output

As you can see in this example, I now have the SSID. I need to repeat the same process to get the password and then return this data in an object. Here is the complete solution:

Image of command output

This code is a simple example of getting the wireless profile password that will work in WMF 3.0 and newer. In WMF 5.0, the ConvertFrom-String was introduced. This cmdlet has a lot of functionality but the feature that I think is totally awesome is the ability to parse text based off a template file. Here is a post on the Windows PowerShell Blog that explains how ConvertFrom-String works: ConvertFrom-String: Example-based text parsing.

The template file consists of sample output. In the sample output, a template markup defines the structure of the data we want to extract. Here is an example taken from the ConvertFrom-String Help file:

Image of command output

Now let’s apply this to output from netsh.

Image of command output

You can see on line 21 that I defined the name of the wireless profile. On line 31, I defined the password. I am only showing one example of output from the template file, but I have two examples of output in my template file. They give the technology ConvertFrom-String is built on (FlashExtract) and a better idea of the text being parsed.

Image of command output

In the previous example, I put it all together. As you can see, I store the path to my template file in $Template. I run netsh.exe and specify to show the profile information for Test-Wireless. I specify key=clear, and this is piped to ConvertFrom-String and I supply an argument for TemplateFile. The results are a PSObject with the name and password for the wireless profile.

Today, I have demonstrated a simple example of how ConvertFrom-String can be used to extract data from a string of text with very minimal code. I encourage you to read PowerShell - Playing with the new ConvertFrom-String cmdlet by PowerShell MVP Francois-Xavier Cat to see how he uses ConvertFrom-String to parse the output from netstat.exe. Additionally, I showed an example of parsing text with the Select-String cmdlet. I would love to hear how you use PowerShell to parse text. Feel free to leave your ideas in the following Comments box.

~Jason

Thanks, Jason! Great ideas!

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use PowerShell to Generate Object with Two Property Types

$
0
0

Summary: Use Windows PowerShell to generate an object with two property types.

Hey, Scripting Guy! Question How can I use Windows PowerShell to generate an object with two property types from simply splitting a string?

Hey, Scripting Guy! Answer Use the ConvertFrom-String cmdlet to pipe a string, and then specify the desired property types, for example:

PS C:\>“123 456” | ConvertFrom-String –PropertyType String,Int

 

Automating Index Fragmentation Reports

$
0
0

Summary: Guest blogger, Laerte Junior discusses how to use PowerShell to automate an index fragmentation report and generate an Excel file.

One of the day-to-day tasks of a DBA is to maintain your environment. This task includes checking the fragmentation of indexes. In this post, I will show how to automate this collection and generate an Excel file.

Environment considerations

For this particular script, I am assuming that like me, you use a central “collector” machine. For that, you have installed SQL Server 2012 or later, Excel, and Windows PowerShell 4.0 or later. Yes! I will use some coding features that only work in Windows PowerShell 4.0 and later. So you are good to go if your checklist looks exactly like this one:

  • Windows PowerShell 4.0 or later
  • SQL Server 2012 or later
  • Coffee and chocolate donuts
  • WooHoo ! Watched the new Star Wars trailer

Coding considerations

Here are some things to consider about running this script:

  • $PSItem and PipelineVariable common parameter are being used. This is why you need PowerShell 4.0 or later. $Psitem is a new syntax to the $_ placeholder in Windows PowerShell 3.0, and PipelineVariablean awesome new common parameter supported in Windows PowerShell 4.0. For more information, see:
  • Invoke-SQLCMD is used. It is a cmdlet supported by the SQLPS provider. In early versions of SQL Server, SQLPS is not a module. In this code, I am importing it as a module, so that is why you need a central repository machine with SQL Server 2012 or later to collect the data.
  • This script is used to automate the collection of the data, so it will be scheduled and called by the Windows Task Scheduler. This means that I need to log everything to check if something bad happened. I am using a Start/Stop transcript for that operation.
  • A list with the SQL Server instance names. The script is using a text file called instancenames.txt. It can be easily changed to query a table in the central repository with the all SQL Server instance names.
  • If a SQL Server instance could not be connected, the script cannot stop. It needs to skip the instance, log, and flow the script to the next instance name. To perform this operation, I am using an  error handle plan with Try-Catch-Finally and the ErrorVariable common parameter.
  • I am not using SMO to retrieve the index fragmentation information. SMO has a known bug and I want to keep my code as simple as I can—not doing tons of conditions to check what version of SQL Server is to use SMO. For more information, see SMO and Index Fragmentation.
  • I am using T-SQL directly with Invoke-SQLCMD to gather the index fragmentation and SMO to connect and run into the databases.
  • KISS–L (keep it simple, stupid Laerte). We are members of a team, and as a team, I need to remember that other people will maintain my code. There are very good best practices documented for coding in shared and corporate environments, but essentially I like to say, “Use common sense.”
  • Tons of verbose and coding comments. Yes. Everything needs to be logged deeply and people need to understand what I did.
  • Do not reinvent the wheel. Check the Windows PowerShell community channels and you will find a lot of very good functions, scripts, and ideas already posted by the top of the PowerShell heroes. Believe me, I live (virtually) with them. They are the best. Literally, the Council Jedi.

Coding flow

The idea of this script is to automate the report of index fragmentation. The control flow is:

  • Run into the SQL Server instance names
  • Connect to the instance
  • Run into the databases
  • Retrieve the index fragmentation to each table
  • Export to CSV files by database
  • Generate an Excel file with the databases from the instance as worksheets. The Excel name will have the name of the instance.

To generate the Excel file from the CSV files, I am using the awesome function from my fellow MVP, Luc Dekens. Check it out: Beyond Export-Csv: Export-Xls.

Here's the code

Note You can download this code from GitHub: LaerteJunior/ScriptingGuysNov2015.

Let's get deep into the code...

Check and configure the location of the script and initial configurations

#getting the location of the script to create the log and instance directories.

#if error , need to stop the script

$scriptRoot = Split-Path-Parent $MyInvocation.MyCommand.Definition

$PSScriptRoot = $scriptRoot

Set-Location $PSScriptRoot

    • Create the paths

try {

            $Creator = @{

                        $True = { $null}

                        $False = {$null = New-Item-Path $Path -ItemType Directory }

            }

            $Path = "$($PSScriptRoot)\Log"

            & $Creator[(Test-Path $Path)]

} catch {

            throw "Could not create the LOG directory"

}

Test to start the script

#Trying to import SQLPS module. If could not, need to stop the script

try {

            import-module SQLPS -DisableNameChecking

} catch {

            throw "Could not import the SQLPS module"

}

#Check to SMO. If could not, need to stop the script

if ([Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") -eq $null -or ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo")  -eq $null)) {

            Throw "SMO not avaliable"

}

Define some variables

#TSQL index fragmentation

$SQLIndex = "

SELECT object_name(IPS.object_id) AS [TableName],

   SI.name AS [IndexName],

   IPS.Index_type_desc,

   IPS.avg_fragmentation_in_percent

FROM sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL , 'DETAILED') IPS

   JOIN sys.tables ST WITH (nolock) ON IPS.object_id = ST.object_id

   JOIN sys.indexes SI WITH (nolock) ON IPS.object_id = SI.object_id AND IPS.index_id = SI.index_id

WHERE ST.is_ms_shipped = 0 "#and IPS.page_count > 1000"

#variable to use to store the instance CSV created. Will use to generate the Excel files.

$PathCSV = @()

#Variable to load the instance names

$InputInstances = "$($PSScriptRoot)\InstanceNames.txt"

#Verbose preference to show evertything

$VerbosePreference = 'Continue'

Load the instance names and check the connection

Try {

            #Loading the Instance Names and storing in the InstanceName Variable by the awesome
               pipelinevariable common parameter

            Get-Content $InputInstances -PipelineVariable InstanceName -ErrorAction Stop  |

            #foreach in the instances

            ForEach-Object {

                        Write-Verbose"Connecting instance $($InstanceName)"

                        try {

                                    try {

                                                #clean the InstanceSkipped variable. It is stored in the write error to skip the instances
                                                  with error

                                                $InstanceSkipped.Clear()

                                                $Connection = new-object ("Microsoft.SqlServer.Management.Common.ServerConnection") $InstanceName 

                                                $Connection.Connect()

                                    } catch {

                                                Write-Error  "Could not connect on instance $($InstanceName) . Error : Error $($_.Exception.Message)" -ErrorVariable InstanceSkipped

 

                                    } finally {

                                                #if the instance was not skipped by any error

                                                if (!($InstanceSkipped)) {

Define some variables and collect the data

#replacing the "\\" by _ to create the paths and csv files

$InstanceNamePath = $InstanceName -replace "\\","_"             

#acumulating the paths to the $pathCSV to use to generate the Excel file and

# storing the current path to the $path variable

$PathCSV += $Path = "$($PSScriptRoot)\Instances\$($InstanceNamePath)"

#removing the path and all inside it if it exits. I am not handling error here or checking if exists do remove, I dont need it

#if not exists and try to remove will generate error that will be supressed and if exists will be removed

Remove-Item -Recurse -Path $Path -Force -ErrorAction SilentlyContinue

New-Item -Path $Path -ItemType Directory -Force

#connecting to the Instance. At this point I know that the instance is online

#and storing the database enumeration to  the DatabaseName Variable using the awesome

#pipelinevariable common parameter

$SQLServer = new-object ("Microsoft.SqlServer.Management.Smo.Server")  $Connection

$SQLServer.Databases  |

Where-Object {!($PSItem.IsSystemObject)} -PipelineVariable DatabaseName |

foreach-object {

            #running the TSQL and saving to a CSV

            invoke-sqlcmd -ServerInstance $InstanceName -Query $SQLIndex -Database $DatabaseName.name
            -verbose:$false |

            Select-Object     @{N='Table Name';E={$_.TableName}},

                                                            @{N='Index Name';E={$_.IndexName}},

                                                            @{N='Index Type';E={$_.Index_type_desc}},

                                                            @{N='Fragmentation';E={$_.avg_fragmentation_in_percent}} |

            Export-Csv "$($Path)\$($DatabaseName.name).csv" -NoClobber -NoTypeInformation

Generate the Excel file

try {

            if ($PathCSV) {

                        $PathCSV |

                        ForEach-Object {

                                    $PathToSave = "$($Psitem)\$(($Psitem -split '\\')[-1]).xlsx"

                                    dir "$($Psitem)\*.csv" |

                                    ForEach-Object {

                                                try {

                                                            $data = Import-Csv $Psitem.fullname

                                                            Export-Xls $data $PathToSave -WorksheetName $Psitem.name -AppendWorksheet

                                                            Write-Verbose "Excel created for $($PathToSave)"

                                                } catch {

                                                            write-Error "Oh Boy..something bad happened. Error : Error $($_.Exception.Message)"

                                                }   

                                    }          

                        }

            }

} catch {

            write-Error "Oh Boy..something bad happened. Error : Error $($_.Exception.Message)"

}

Schedule and register the script

I am using scheduled jobs in Windows PowerShell 3.0, so it just creates the trigger:

$trigger = New-JobTrigger -Daily -At 1am

Register the job in the Windows Task Scheduler by pointing to the .ps1 file:

Register-ScheduledJob -Name EnergyAnalysisJob -Trigger $trigger -FilePath 'C:\laerte\scripting guys\indexes.ps1'

For more information, see Scheduling Background Jobs in Windows PowerShell 3.0.

That is it. Thanks to my good friend, Ed Wilson, for allowing me to talk a little bit about my passion: automation!

~Laerte

Thanks, Laerte. Awesome 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

PowerTip: Store Current Pipeline Value in Variables with PowerShell

$
0
0

Summary: Use PowerShell to store current pipeline values in variables.

Hey, Scripting Guy! Question How can I use Windows PowerShell to improve my code by storing the current pipeline value into variables?

Hey, Scripting Guy! Answer Use the new PipelineVariable common parameter, for example:

Get-Counter -ListSet 'LogicalDisk','SQLServer:Buffer Manager','SQLServer:Memory Manager' -PipelineVariable CounterCategory | 

Select-Object -ExpandProperty Counter -PipelineVariable CounterName |

Where-Object {$CounterName -match '(sec/Transfer|Avg. Disk Queue Length|Buffer Cache|CheckPoint|Target Server|Total)'} | 

Select-Object   @{E={$CounterCategory.CounterSetName};N="CounterSetName"},

                @{E={$CounterCategory.Description};N="Description"},

                @{E={$CounterName};N="Counter"}

Note  PipelineVariable was introduced in Windows PowerShell 4.0.

Introducing the PowerShell Excel Module

$
0
0

Summary: Guest blogger, Doug Finke talks about his PowerShell Excel module.

The PowerShell Excel Module is a brand new, exciting, and better way to interact with Microsoft Excel from Windows PowerShell. Plus for bonus points, you don’t need Excel installed on the target machine to create the spreadsheet. Many users of this module generate Excel spreadsheets on servers, and then others in the company pick up the reports from a central server. Or for the last step in their script, they can mail the .xlsx file.

The challenge

Until now, there have been a few ways to get data into Excel. One way is to create a comma-separated value file (.csv) by using Export-Csv, and then open it in Excel, for example:

Get-Process | Export-Csv –NoType c:\Temp\ps.csv

Invoke-Item c:\Temp\ps.csv

Another way to get data into Excel is to remotely control Excel. Use the Excel COM interface to spin it up, create a workbook or a worksheet, and then loop through your data to push it into the appropriate cells. You need to create headers and add the data to the correct row and column.

Here’s a snippet that creates Excel, makes it visible, and then adds a workbook:

$xl = New-Object -ComObject Excel.Application

$xl.Visible = $true

$xl.Workbooks.Add()

An alternative is to use .NET and Open Database Connectivity (ODBC). It takes some set up, and you need to write the looping and poking in the same way as the COM interface example. This approach is like working with SQL Server data.

Enter Office, open XML

What if you could just do this?

Get-Process | Export-Excel c:\temp\ps.xlsx –Show

This example creates a ps.xlsx file, a workbook, a worksheet, a header row, and organizes all the data in rows and columns. The –Show parameter launches Excel and opens the ps.xlsx file.

Image of spreadsheet

This is great (and it works with any data in PowerShell). The flat data is important, and so are the visuals.

Kick it up a notch

What if you could produce the following visual image? The PowerShell Excel module lets you create Excel pivot tables and charts from the transformed data. From the data generated above (and stored in a separate spreadsheet in the same workbook), you can easily create a pivot table and a chart.

Image of spreadsheet

Here’s the script:

Get-Process | Where Company |

    Export-Excel C:\Temp\ps.xlsx -Show `

    -IncludePivotTable -PivotRows Company -PivotData @Handles=”sum”}`

    -IncludePivotChart -ChartType PieExploded3D

The IncludePivotTable and IncludePivotChart cmdlets generate the pivot table and chart. ChartType lets you pick what type of chart you want (there are many to choose from). The PivotRows and PivotData parameters describe how to tabulate the data.

If you run Get-Process, you’ll see each process running on your system. The pivot table in Excel groups the information by using PivotRows and calculates measurements with PivotData. Here you tell it to sum the number of handles. In the previous image, the number of handles are totaled from Get-Process and grouped by company. Now you can see that the processes running from Microsoft Corporation have almost 50 K handles.

That’s the quick tour

I wrote the Excel module to plug in to the PowerShell ecosystem so you can easily export any data to Excel just as you would a .csv file. You can pipe the results of an SQL Server query or a REST API—the list goes on and on.

There is a lot more to this module to check out. Try the Import-Excel function, which lets you read an Excel spreadsheet and convert it to PowerShell objects. It lends itself to taking existing spreadsheets, applying a calculation (or adding more data from a look up), and quickly creating a new spreadsheet. Here is an example:

Import-Excel sales.xlsx |

    ForEach { “calc projections” } |

    Export-Excel futureSales.xlsx

 

Where to get it?

There are a couple of ways that you can download and install this module.

PowerShell Gallery  If you are running Windows PowerShell 5.0, you can use the new Install-Module ImportExcel command. It’ll pull down the module from the gallery.

GitHub  You can also get it from GitHub: dfinke/ImportExcel.

A few words about GitHub...

You can Starthe project if you like it. You can also open issues if you have questions or find issues. Plus, you can install the module from GitHub. You can also clone or fork the project. This lets you make modifications that you want. If you want to share them, you can create a Pull Request to add it to the core project.

This PowerShell module makes you more productive. Plus it highlights how to interact with .NET DLLs and build complex functions. The project has grown in several ways, based on the community contributing updates to the scripts and making great suggestions and feature requests.

Join in! Hop over to GitHub and post what you’d like to see and how you’re working with it.

~Doug

Thanks, Doug. This is such a useful module.

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

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Create Function List and Find PowerShell Files

$
0
0

SummaryUse Windows PowerShell to create a function list and find files.

Hey, Scripting Guy! Question How can I use Windows PowerShell to avoid repetitively typing “ls . –r *.ps1” to get a list of PowerShell files and then
           “ls . –r *.ps1 | sls function” to search the content of those files for a string?

Hey, Scripting Guy! Answer Add this function to your PowerShell profile:

function fps ($find) {    

    "Get-ChildItem . -Recurse *.ps1 $(if($find) { "| Select-String $find" })" |

        Invoke-Expression

}

Then, if you type `fps`, you’ll get a list of .ps1 files. To search for the string functions in each file, type `fps function`.

Beginning Use of PowerShell Runspaces: Part 1

$
0
0

Summary: Boe Prox presents some tips about beginning use with runspaces.

Honorary Scripting Guy and Cloud and Datacenter Management MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. Today I begin a 3-part series about working with PowerShell runspaces. I'll finish up with a fourth post that talks about a module I put together that makes working with runspaces as simple as using some similar commands that you are already familiar with.

Let's say you are working in PowerShell and you want to kick off something to run in the background while you are working on some other things in the console. You would usually use something related to PSJobs, such as the *-Job cmdlets. Or you might use the –Asjob parameter that you see in some cmdlets, such as Get-WMIObject.

The main complaint in some of these efforts is that this causes another PowerShell process to be created and loaded with all of the format and type files, creating the PSDrives and such. This can take up a lot of resources depending on how many jobs you plan to run.

Using runspaces is a great alternative to get around this issue. Runspaces create a new thread on the existing process, and you can simply add what you need to it and send it off running. The downside is the level of effort that is required to create the runspace and the PowerShell instance to run it. Additionally, you have to manage the whole process by kicking off the instance and monitoring it for completion. Then you need to retrieve the information (if applicable) and tear down the runspace and PowerShell instance for disposal.

Using the built-in jobs framework makes this easy because you simply run something like Start-Job and then Receive-Job to get the data. Everything else happens behind the scenes. My plan is to take difficulty that may appear with using runspaces and show you how quickly and simply you can kick off a command in a runspace and pull it back without breaking a sweat.

To start off, I will make use of the [powershell] type accelerator and use the Create() method to quickly create the instance. From there, I'll add a script block and kick off the instance. Note that this already has a runspace built-in, so I do not have to worry about creating a new runspace at this point.

$PowerShell = [powershell]::Create()

[void]$PowerShell.AddScript({

    Get-Date

})

$PowerShell.Invoke()

The result of running this is simply the return of the current date and time. Also look at where I used [void] so that it doesn’t pollute my pipeline or anywhere in the output. What you may not know is that this is no different than what would run if I typed Get-Date in my console—which is not really useful if we want a way to run things in the background. Add a Start-Sleep –Seconds 10 in my example script block, and you will see what I mean.

Before I jump into some better multithreading examples, I am going to show the same example, but this time I am creating a runspace to work with and adding it to the PowerShell instance.

$Runspace = [runspacefactory]::CreateRunspace()

$PowerShell = [powershell]::Create()

$PowerShell.runspace = $Runspace

$Runspace.Open()

[void]$PowerShell.AddScript({

    Get-Date

})

$PowerShell.Invoke()

I need a better way to run a command in the background so the console can be open to do other things. Instead of using Invoke() to kick off our commands, I will instead use BeginInvoke(). This will give back an async object, which can be used to monitor the state of our currently running background command.

Assuming that everything works out, we will eventually see that the background command has ended and I am free to retrieve the output and dispose of the PowerShell instance (so I do not have the possibility of memory leaks or something else).

First things first. Let’s kick off a background command and get back the object so we can track it.

$Runspace = [runspacefactory]::CreateRunspace()

$PowerShell = [powershell]::Create()

$PowerShell.runspace = $Runspace

$Runspace.Open()

[void]$PowerShell.AddScript({

    Get-Date

    Start-Sleep -Seconds 10

})

$AsyncObject = $PowerShell.BeginInvoke()

We can now inspect our Async object and view its status to see how things are going.

$AsyncObject

Image of command output

Based on the IsCompleted property, the command is currently running in the background, so I need to wait a little bit longer and then check again. When it shows True, I can begin processing the data and tearing down the instance.

Let’s assume that this has finally completed, and now I need to get the output. First I need to use the async object that was saved as the key to unlock the door on the PowerShell instance when I call EndInvoke(). As soon as I do this, the output is displayed. It is a good idea to save the output for future use.

$Data = $PowerShell.EndInvoke($AsyncObject)

Next up is to clean up by properly disposing of the instance. Nothing too crazy, but it is definitely necessary to ensure that I do not set myself up for disaster by having resource issues.

$PowerShell.Dispose()

And that is it for today and beginning with PowerShell runspaces. Tomorrow, I'll build on what we learned by incorporating parameters and arguments into runspaces.

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

Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy


PowerTip: Find Current Runspaces in PowerShell

$
0
0

Summary: Boe Prox shows how to find the current runspaces in Windows PowerShell.

Hey, Scripting Guy! Question  How can I list all of the currently running runspaces in Windows PowerShell?

Hey, Scripting Guy! Answer Use the Get-Runspace cmdlet in Windows PowerShell 5.0:

Get-Runspace

Id Name      ComputerName    Type      State       Availability

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

 1 Runspace1       localhost       Local         Opened        Busy

 2 Runspace2       localhost       Local         Opened        Busy

 3 Runspace3       localhost       Local         Opened        Busy

 4 Runspace4       localhost       Local         Opened        Busy

Beginning Use of PowerShell Runspaces: Part 2

$
0
0

SummaryBoe Prox presents some tips about beginning use with runspaces.

Honorary Scripting Guy and Cloud and Datacenter Management MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. Today I begin a 3-part series about working with PowerShell runspaces. I'll finish up with a fourth post that talks about a module I put together that makes working with runspaces as simple as using some similar commands that you are already familiar with.

Yesterday in Beginning Use of PowerShell Runspaces: Part 1, we spent some time looking at how we can create a runspace that can be used to run a command in the background while leaving our console wide open for us to continue running commands. Although using what we have learned will make it easier to push commands to the background, we are currently limited to only running what we have in the script block.

This means that we have to throw everything into the script block to run. This doesn’t allow us to dynamically create variables or to have parameters to use to run against a collection of items. Fortunately, we have this capability when working with runspaces, but we have to make sure that everything is done correctly; otherwise, we may have some unforeseen issues.

Why can’t we simply create variables outside of the script block, run the commands in the runspace, and wait for our results to come rolling in? Well, the following example shows how this might not fare so well.

$PowerShell = [powershell]::Create()

$Global:Param1 = 'Param1'

$Global:Param2 = 'Param2'

[void]$PowerShell.AddScript({

    [pscustomobject]@{

        Param1 = $Param1

        Param2 = $Param2

    }

})

#Invoke the command

$PowerShell.Invoke()

$PowerShell.Dispose()

Image of command output

Yes, the Global is intentional to show you that regardless of the type of scope that you give the variable, it will not work within the runspace. It simply outputs an object that consists of nothing useful.

To get the desired results, we need to make use of a method called AddArgument(), and give it our data. There are some things to keep in mind when using this approach. First, you must have a Param() statement within your script block so it can populate the variables. Even more importantly, you must be aware of the order that you supply the arguments with each AddArgument() method. If you switch the order of what is being added, you will have issues, for example:

$Param1 = 'Param1'

$Param2 = 'Param2'

$PowerShell = [powershell]::Create()

[void]$PowerShell.AddScript({

    Param ($Param1, $Param2)

    [pscustomobject]@{

        Param1 = $Param1

        Param2 = $Param2

    }

}).AddArgument($Param2).AddArgument($Param1)

#Invoke the command

$PowerShell.Invoke()

$PowerShell.Dispose()

Image of command output

This doesn’t work out that well. Order is absolutely important in this approach. This code will work out much better:

$Param1 = 'Param1'

$Param2 = 'Param2'

$PowerShell = [powershell]::Create()

[void]$PowerShell.AddScript({

    Param ($Param1, $Param2)

    [pscustomobject]@{

        Param1 = $Param1

        Param2 = $Param2

    }

}).AddArgument($Param1).AddArgument($Param2)

#Invoke the command

$PowerShell.Invoke()

$PowerShell.Dispose()

This is nice, but we can make it a little easier by using the AddParameter() method instead. This gives us an easier approach to specifying the proper variables with the parameters without the need to worry about the order of adding the data. The only thing that you must be aware of is that the parameter name must match what is in the Param() statement in the script block so it maps correctly.

We have two options for adding parameters. The first is to individually add them, such as .AddParameter(‘Param1’,$Param1). This works, but if you start adding several parameters to a runspace, you may have to get creative with how you format your code.

$Param1 = 'Param1'

$Param2 = 'Param2'

$PowerShell = [powershell]::Create()

[void]$PowerShell.AddScript({

    Param ($Param1, $Param2)

    [pscustomobject]@{

        Param1 = $Param1

        Param2 = $Param2

    }

}).AddParameter('Param2',$Param2).AddParameter('Param1',$Param1) #Order won't matter now

#Invoke the command

$PowerShell.Invoke()

$PowerShell.Dispose()

Image of command output

A more useful approach is to create a hash table with the parameter name and its value. Then supply that to the AddParameters() method, which will take each item in the hash table and map them to what you have in Param().

$ParamList = @{

    Param1 = 'Param1'

    Param2 = 'Param2'

}

$PowerShell = [powershell]::Create()

[void]$PowerShell.AddScript({

    Param ($Param1, $Param2)

    [pscustomobject]@{

        Param1 = $Param1

        Param2 = $Param2

    }

}).AddParameters($ParamList)

#Invoke the command

$PowerShell.Invoke()

We have the same output as we got previously, but now have better looking code to go along with it!

Now we have covered how to handle parameters within a runspace. Tomorrow, we will continue our dive into runspaces by looking at runspace pools and the world of multithreading our commands!

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

Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Get the Async Object Created by BeginInvoke

$
0
0

Summary: Boe Prox shows how retrieve a seemingly lost Async object from a runspace.

Hey, Scripting Guy! Question How can I get the seemingly lost Async object created by BeginInvoke?

Hey, Scripting Guy! Answer Use Reflection to pull this information out of a Runspace object and get the Async object, which is required
           to use with EndInvoke().

$Runspace = [runspacefactory]::CreateRunspace()

$PowerShell = [powershell]::Create()

$Runspace.Open()

$PowerShell.Runspace = $Runspace

[void]$PowerShell.AddScript({

    [pscustomobject]@{

        Name = 'Boe Prox'

        PowerShell = $True

    }

})

#Intentionally forget to save this

$PowerShell.BeginInvoke()

#Time to retrieve our missing object

$BindingFlags = [Reflection.BindingFlags]'nonpublic','instance'

$Field = $PowerShell.GetType().GetField('invokeAsyncResult',$BindingFlags)

$AsyncObject = $Field.GetValue($PowerShell)

#Now end the runspace

$PowerShell.EndInvoke($AsyncObject)

Beginning Use of PowerShell Runspaces: Part 3

$
0
0

Summary: Boe Prox shows us some tips about using runspace pools for multithreading.

Honorary Scripting Guy and Cloud and Datacenter Management MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. Today I continue a 3-part series about working with PowerShell runspaces. I’ll finish up with a fourth post that talks about a module I put together that makes working with runspaces as simple as using some similar commands that you are already familiar with. You might enjoy reading these posts first:

After spending the past couple of days working with runspaces and looking at how we can use parameters and arguments to supply variables to our runspaces, we are taking the next step in our journey by looking at runspace pools to do some multithreading with multiple commands. The process for working with runspace pools is similar to working with runspaces. There are a few minor differences in how we use the runspace pools with the PowerShell instance.

By using a runspace pool, we cannot only run multiple commands at the same time (which we could do using multiple runspaces), but we now have the capability to throttle the number of runspaces that are running concurrently. For example, I may have 50 items that I need to scan, but because of resource limits, I can only run six at a time. I queue up all of the objects, but only six will run at a given time. A new one will start when another one ends.

We can create the runspace pools by using the CreateRunspacepool() method from the [runspacefactory] type accelerator. We’ll provide some additional data during this construction that tells the runspace pools the limits for minimum runspaces and the maximum runspaces that can be allowed to run at a time (our throttle).

I am going to set up some parameters because I want to show how you can add that data (just like we did with runspaces):

$Parameters = @{

    Param1 = 'Param1'

    Param2 = 'Param2'

}

Next I will build out the runspace pools with a throttle of 10 and open the RunspacePool property so I can start working with it to add my script block.

#region Runspace Pool

[runspacefactory]::CreateRunspacePool()

$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()

$RunspacePool = [runspacefactory]::CreateRunspacePool(

    1, #Min Runspaces

    5 #Max Runspaces

)

$PowerShell = [powershell]::Create()

#Uses the RunspacePool vs. Runspace Property

#Cannot have both Runspace and RunspacePool property used; last one applied wins

$PowerShell.RunspacePool = $RunspacePool

$RunspacePool.Open()

#endregion

With that accomplished, I can move forward to start running commands against my systems (or items) by creating a script block with my commands:

$jobs = New-Object System.Collections.ArrayList

1..50 | ForEach {

    $PowerShell = [powershell]::Create() 

    $PowerShell.RunspacePool = $RunspacePool   

    [void]$PowerShell.AddScript({

        Param (

            $Param1,

            $Param2

        )

        $ThreadID = [appdomain]::GetCurrentThreadId()

        Write-Verbose "ThreadID: Beginning $ThreadID" -Verbose

        $sleep = Get-Random (1..5)       

        [pscustomobject]@{

            Param1 = $param1

            Param2 = $param2

            Thread = $ThreadID

            ProcessID = $PID

            SleepTime = $Sleep

        } 

        Start-Sleep -Seconds $sleep

        Write-Verbose "ThreadID: Ending $ThreadID" -Verbose

    })

    [void]$PowerShell.AddParameters($Parameters)

    $Handle = $PowerShell.BeginInvoke()

    $temp = '' | Select PowerShell,Handle

    $temp.PowerShell = $PowerShell

    $temp.handle = $Handle

    [void]$jobs.Add($Temp)   

    Write-Debug ("Available Runspaces in RunspacePool: {0}" -f $RunspacePool.GetAvailableRunspaces())

    Write-Debug ("Remaining Jobs: {0}" -f @($jobs | Where {

        $_.handle.iscompleted -ne 'Completed'

    }).Count)

}

There is a bit happening here that I need to explain. First, you probably noticed the ArrayList that I created to handle the jobs. I do this because as I add a new command to run in the PowerShell instance that uses RunspacePool, I need to call BeginInvoke() to kick off the command (if it is being helped due to throttling). That kicks back an Async object that I need to monitor for completion.

Also, I chose the script block that displays a simple object with some threading information. These work in a throttle of 10 items (threads) and will never use additional threads because the default ThreadOption for RunspacePool is ReuseThread.

Now that I have run all of my runspace jobs, I can sit back and wait for them to finish. I can check on them and when they are finished, I call EndInvoke() on each one to return the results of my scan.

#Verify completed

Write-Debug ("Available Runspaces in RunspacePool: {0}" -f $RunspacePool.GetAvailableRunspaces())

Write-Debug ("Remaining Jobs: {0}" -f @($jobs | Where {

    $_.handle.iscompleted -ne 'Completed'

}).Count)

$return = $jobs | ForEach {

    $_.powershell.EndInvoke($_.handle)

    $_.PowerShell.Dispose()

}

$jobs.clear()

And for fun, I can look at the data to see that I only used a throttle of 10:

$return | Group ProcessID | Select Count, Name

$return | Group Thread | Select Count, Name

($return | Group Thread).Count

As we can see, my throttle of 10 was respected during the run against 50 items. I never went over that limit, and I was able to successfully perform my task.

Tomorrow I will wrap up my series about PowerShell runspaces by showing a module that I put together. It can allow you to use runspaces and runspace pools with ease by giving a familiar look and feel to them. Stay tuned!

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

Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Add Custom Function to Runspace Pool

$
0
0

Summary: Boe Prox shows how to add a custom function to a runspace pool.

Hey, Scripting Guy! Question How can I use Windows PowerShell to add a custom function to a runspace pool?

Hey, Scripting Guy! Answer Use the following approach:

#Custom Function

Function ConvertTo-Hex {

    Param([int]$Number)

    '0x{0:x}' -f $Number

}

#Get body of function

$Definition = Get-Content Function:\ConvertTo-Hex -ErrorAction Stop

#Create a sessionstate function entry

$SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry
-ArgumentList 'ConvertTo-Hex', $Definition

#Create a SessionStateFunction

$InitialSessionState.Commands.Add($SessionStateFunction)

 #Create the runspacepool by adding the sessionstate with the custom function

$RunspacePool = [runspacefactory]::CreateRunspacePool(1,5,$InitialSessionState,$Host)

Viewing all 3333 articles
Browse latest View live




Latest Images