Welcome to MSDN Blogs Sign in | Join | Help

Windows PowerShell and the Windows Management Framework

There’s been a lot of great excitement that’s accompanied the release of PowerShell V2 and Windows Remote Management (WinRM) – also known as the Windows Management Framework. We’ve also heard the occasional question on whether it’s possible to install them independently.

When we’ve heard this concern, it is usually focused on security. To be clear, Windows Remote Management (WinRM) has been part of Windows since Vista and Server 2008. It does not listen to network connections by default, and must be explicitly activated. Both have advanced greatly during the release of Windows 7 – most notably by working together to support a rich PowerShell-based remoting experience.

The Windows Management Framework download (PowerShell + WinRM) simply updates the binaries on non-Win7 machines to bring them up to the same version already included in Windows 7 and Windows Server 2008 R2.

Investigating this concern further, it usually comes down to concern about increased network attack surface: automatically opening a network port to accept incoming connections. Installing the Windows Management Framework does not do this automatically. “Secure by Default” is the mantra of both our team, and Microsoft as a whole. Enabling PowerShell Remoting is an explicit step that must be run from an elevated prompt. The command fully informs you of the security implications when you do so:

[C:\Windows\system32]
PS:101 > Enable-PsRemoting

WinRM Quick Configuration

Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.

This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

While we (and Windows Security, and external security consultants that we hired for analysis and penetration testing) also believe in the security of our remoting protocol and the attack surface that it exposes, we focused from the start on letting you make that decision independently.

 

Lee Holmes [MSFT]
Windows PowerShell Development
Microsoft Corporation

PDC09 SVR12 and SVR13 Session Demos

Attached are the demos from the PDC sessions SVR12 and SVR 13 today. Here are the steps you need to do before you can run them

1. Unzip the files and run setup\setup.ps1. This will configure user accounts and initialize some files required

2. Change computers.dat in service\roles\. Set it to the computers that you want to connect to

3. Edit Impersonation\Get-DelegationCredential.ps1 under each folder - Inventory, Service and Employee (Please Note: This script is intended only for demo purposes and is not recommended to be copied for production

4. Set the paths in app.xaml.cs in each of the visual studio projects - EasyGUI and RemoteUI

Once the above are done then you are all set. Play around with them and have fun !!!

Cheers

Nana

---

Narayanan Lakshmanan [MSFT]
Developer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Posted by PowerShellTeam | 4 Comments

Attachment(s): PDC09.zip

I Can Do That With 1 Line of PowerShell: Installed Software

Ying Li has a cool PowerShell Script to list installed Software on a local computer HERE

When I looked at it and thought to myself, I can do that with 1 line (if I cheat a little).  Here it is:

PS> gp HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |Select DisplayName, DisplayVersion, Publisher, InstallDate, HelpLink, UninstallString |ogv

 

Here is what  you get for that 1 line of code:

image

 

I cheated by using OGV  (Out-GridView).  Ying used Excel and the output is nicer.  But when you only want to spend a single line…  :-)

 

Experiment!  Enjoy!  Engage!

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

SRV312 TechEd Slides

I just gave the talk, "”Server Management Improvements and Solutions for Windows Server 2008 R2”.  It went over rather well.  Attached are the slides.  I will try to get you the demos but that will be difficult and will take some time.

Enjoy!

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Posted by PowerShellTeam | 8 Comments

Attachment(s): SRV312_Snover.pptx

Windows Server 2008 R2 Rocks!

One of the things I like to say is that, “Microsoft is incapable of sustained error”.  By that I mean that Microsoft is an intensely self-conscious culture, fearless about confronting shortcomings and  constantly looking for ways to do things better.  We beat ourselves up pretty brutally about the shortcomings of Vista and committed ourselves to doing better going forward.  This is one of the reasons why Windows Server 2008 was such a good release.  We didn’t stop there, we raised the bar for Windows 7 and Windows Server 2008 R2.

Part of that was the product itself.  Anyone that uses either of these products can see that immediately.  These are solid, high quality products.  We also raised the bar and teaching people about the release. 

This morning, MSDN’s Channel9 opened up a new site for learning about WS08/R2 HERE.  This site has lessons and short training videos for the following WS08/R2 topics:

Much of this content if focused on developers but admin/It Pro scripters will find PowerShell section useful and appropriate.

Experiment!  Enjoy!  Engage!

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

PowerPack Challenge

Quest Software’s PowerPack Challenge’09 is on... calling all script warriors to show their skills.

See the details here - http://powergui.org/powerpackchallenge.jspa

Happy Scritping

Osama Sajid, Program Manager

Why is Get-ChildItem so Slow?

We get this question fairly frequently when it comes to slow network connections.

The performance of directory listings (especially on a laggy network) are limited by the .NET APIs we call to retrieve the directory information. There are two limitations to the current set of APIs:

Forced Retrieval of Attributes

When we do a directory listing, we show the standard attributes of the file or directory: Mode, LastWriteTime, Length, and Name. The core Windows API is highly optimized for this basic scenario, and returns these attributes by default along with the rest of the file information. However, the .NET Framework doesn’t take advantage of this data, and instead goes back to the network location to ask for all of the file attributes. This chatty behaviour adds a handful of network round trips for each file or directory, making the directory listing many times slower: hundreds or thousands of times slower in many cases. The Framework team addressed this as part of .NET 4.0, and you’ll see the benefits of this new feature as soon as we are able to adopt it.

In version two, even without the benefit of the new .NET API, though, you’ll see a huge improvement in wildcarded directory listings (both local and remote.)

As a background, PowerShell wildcards are different than straight cmd.exe wildcards. For example, PowerShell wildcards do not match the 8.3 short file name, while the native filesystem filtering (exposed by cmd.exe wildcards) do. PowerShell’s wildcards support character ranges, while the native file system filtering support does not. Because of this, PowerShell wildcard processing happens AFTER we’ve retrieved all of the files.

This comes at a cost, however. Native file system filtering (as exposed by the –Filter parameter) is MUCH faster, as its processing is wired into the Windows file system.

In version two, we did a bunch of work to resolve this strain. When you provide a PowerShell wildcard, we convert as much of it as possible to a native filesystem filter, and then apply our wildcarding logic to the much smaller set of results. You’ve probably noticed this most in tab completion, but it makes huge improvements in regular wildcarded directory listings. Especially remote ones. Since the native filtering is processed by the remote file system, we don’t need to suffer the performance penalty of accessing attributes of files that you ultimately don’t care about anyways. In version one, you can work around the issue by specifying the –Filter parameter directly. If this still doesn’t provide the speed you need, you can call “cmd.exe /c dir”.

 

Lack of Enumeration API

This issue raises itself for directory listings that contain many files. The DirectoryInfo.GetFiles() method returns an array. When creating that result list, the .NET Framework does many re-allocations (and copies) of that array, causing an exponential performance degradation:

GetChildItemPerf

 

This, too, has been resolved in the .NET 4.0 updates, by offering an API that lets you enumerate through a directory result, rather than retrieve them all at once. If you are running into these limitations, you can again apply a wildcarding approach. If this still doesn’t provide the speed you need, you can call “cmd.exe /c <command>”.

Why Don’t We Fix It?

Since cmd.exe isn’t impacted by these issues, why don’t we just do the same thing and call into the core Windows APIs directly? The reason is twofold:

  1. A core tenet of PowerShell is providing access to the REAL underlying .NET objects. If we implemented the semantics ourselves, we’d have to return new types of objects – something like PSFileInfo and PSDirectoryInfo. V1 scripts (or downstream cmdlets) that expect the REAL underlying .NET objects would fail to work. While we could add a new switch (-Raw?), users would still have to change their scripts to support it. In that case, they might as well use the existing cmd /c workaround.
  2. This issue is ultimately transient. While it’s annoying to drag out over a few years, it will ultimately come and go without users having to change their behaviour. One day, you’ll install a build and the issues will just magically be gone.

Again, thanks for your continuing feedback. That’s what ultimately helped us discover the issue and make sure the right people knew about it.

 

Lee Holmes [MSFT]
Windows PowerShell Development
Microsoft Corporation

Workaround for Add-Member on plain .Net objects

Introduction

While I was fixing bugs, I came across an interesting bug (https://connect.microsoft.com/PowerShell/feedback/ViewFeedback.aspx?FeedbackID=382818) that seemed to have a very simple fix upon first glance. However, after doing some more research, I found out that it was actually very tricky to fix the root cause. If you do the following:

$table = @{ key1="val1"; key2="val2" }
Add-Member -in $table -MemberType NoteProperty -Name test -Value testValue

Now if you pipe $table to Get-Member you will notice that the test property is missing. No matter how many times you repeat the same Add-Member operation, $table seems to be totally unaffected. However, if you access the test property or even a nonexistent property before you use Add-Member, the command will behave correctly.

Why is this happening?

In PowerShell, we create .Net objects as System.Object as opposed to System.Management.Automation.PSObject for performance reasons. This is because every time an object is converted from System.Object to PSObject, PowerShell has to build the metadata information by reflecting on the .Net object. Doing so can introduce unnecessary overhead since not all operations require PSObjects. When you do something with it (i.e. property access), it becomes a PSObject. However, when you pass a variable ($table) bound to an Object to a cmdlet such as Add-Member, the cmdlet internally converts the bound object into a PSObject. This effectively creates a copy of the original object and anything done to the PSObject is not reflected on the input object.

How can we work around it?

We are hoping to fix the root cause in the next release. However, the workaround is actually quite simple. Since Add-Member is able to emit the modified object if the –PassThru flag is specified, all you have to do is assigned the object back to your initial variable.

$table = Add-Member -in $table NoteProperty Test "Test property" –PassThru

Tianjie (James) Wei [MSFT]
Software Design Engineer
Windows PowerShell Team

Quick, Dirty, Super-Useful Scripting

Last weekend I installed the super-awesome W7 Ultimate on all the machines at home.  This weekend I decided to install the XP Virtual mode download.  I started to do this and noticed that it was 500MB.  That is large and is going to take a long time FOR EACH PC I have.  One of the reasons I got the Ultimate SKU is that it has BranchCache.  This is EXACTLY what Branchcache was designed to do.  One PC downloads something and puts it into a distributed cache and then when the other PCs go to download it, they get it from their peers instead of from the internet (given the way my son reacts when my downloads affects the network and  his ability to kill Nazi Zombies, I’m motivated to minimize the usage of the network :-) ).

That sounds great but the Branchcache is not enabled by default so you have to set it up.  I was doing this and it was a pain in the butt so I decided to experiment with a quick and dirty script and I LOVE the results so I thought I would share.

The issue is that they way you configure Branchcache is with NETSH.  The problem with that is that NETSH isn’t remotable.  I was going back and forth between machines trying things out.  I gave myself a dopeslap and I realized what I was doing and established a remote PowerShell session to the machines.  For a while I was doing Enter-PSSession which gives you an interactive session to the remote machine.  That was great but I have more than one machine so I either had to switch machines or have a couple of windows open.  Then I decided I would use Invoke-Command.  I created a remote connection to all the machines and then collected them in a variable $s and used ICM.

PS> $s = GSN
PS> icm $s {netsh branchcache show localcache}

That worked great but it was a little clumsy to work with so I decided to write a quick-n-dirty function to make my life better.

function b { icm $s -ScriptBlock $([scriptblock]::create("netsh branchcache $args "))}

Notice that I’m not using VERB-NOUN.  Why should I?  I’m after FAST (minimal typing) and this is a throw away function.  “B” is perfect.

Notice that I didn’t declare parameters.  Why should I?  I’m going quick.  I need it to work but I don’t need it to be formal.

Notice that I use positional parameters and aliases?  Why not? 

Now what I’m able to do is:

PS> b show localcache
PS> b set service mode=DISTRIBUTED

<UPDATE>
I realized from the comments that I didn't deliver the punchline.  The WHOLE point was that the command is going to run on ALL the machines!  THAT's the crazy wonderful part of it.
</UPDATE>

I haven’t got Branchcache working yet but I’m am zooming and having a blast.   We spend a lot of time talking about best practices and formal scripting.  That is all super great and super important but there is NOTHING wrong with quick-n-dirty scripting for adhoc functions.  The fact that we designed it so that you can do both with PowerShell is a source of great pride.

GOD I LOVE POWERSHELL!

Experiment!  Enjoy!  Engage!

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Sending Automated emails with Send-MailMessage, ConvertTo-HTML, and the PowerShellPack’s TaskScheduler module

On October 15th I released a large collection of scripts called the PowerShellPack.  The PowerShellPack has tons of PowerShell V2 scripts that can be used to do all sorts of fun and practical things.  Today, we’ll show how to use a module in the PowerShell Pack to schedule sending a daily automated email with information about the installed programs on a given system.

Turning the output of a PowerShell script into an automated email is a snap in PowerShell V2, thanks to a nifty new cmdlet called Send-MailMessage.  Send-MailMessage sends out emails with the credentials of the current user (or an arbitrary user) and an SMTP server.  Send-MailMessage can send HTML emails by using the –BodyAsHtml switch, and ConvertTo-HTML can take the output of a cmdlet and turn it into an HTML chunk.  If the email needs some introduction and a signature, you can always use ConvertTo-HTML’s new –PreContent and –PostContent parameters.

Here’s a quick chunk of script that will send the email message once.  Notice that instead of having a very long line with a lot of parameters, I keep the parameters to Send-MailMessage in a Hashtable and use Splatting to provide them to Send-MailMessage

            $messageParameters = @{                        
                Subject = "Installed Program report for $env:ComputerName.$env:USERDNSDOMAIN - $((Get-Date).ToShortDateString())"                        
                Body = Get-WmiObject Win32_Product |                         
                    Select-Object Name, Version, Vendor |             
                    Sort-Object Name |             
                    ConvertTo-Html |                         
                    Out-String                        
                From = "Me@MyCompany.com"                        
                To = "Me@MyCompany.com"                        
                SmtpServer = "SmtpHost"                        
            }                        
            Send-MailMessage @messageParameters -BodyAsHtml                        

Now that we've got that chunk of code down, let's go ahead and make sure that any problems we encounter get put into a file somewhere.  I can do this by setting one of PowerShell’s magic variables $ErrorActionPreference = “Stop”, and adding a try/catch around all of my code:

        $ErrorActionPreference = "Stop"                        
        try {                        
            $messageParameters = @{                        
                Subject = "Installed Program report for $env:ComputerName.$env:USERDNSDOMAIN - $((Get-Date).ToShortDateString())"                        
                Body = Get-WmiObject Win32_Product |                         
                    Select-Object Name, Version, Vendor |             
                    Sort-Object Name |             
                    ConvertTo-Html |                         
                    Out-String                        
                From = "Me@MyCompany.com"                        
                To = "Me@MyCompany.com"                        
                SmtpServer = "SmtpHost"                        
            }                        
            Send-MailMessage @messageParameters -BodyAsHtml                        
        } catch {                        
            $_ |                         
                Out-File $env:TEMP\ProblemsSendingHotfixReport.log.txt -Append -Width 1000                        
        }

Finally, I’ll just go ahead and use the TaskScheduler module from PowerShellPack to make sure the email arrives every day.  To do this, I’ll need to download the PowerShellPack to the machine I’ll be scheduling the task on.   Scheduling a task with the PowerShell Pack involves a short pipeline:  Ours will look something like:

New-Task |            
    Add-TaskAction -Script {            
        # Our Emailer Here            
    } |            
    Add-TaskTrigger -Daily -At "9:00 AM" |             
    Add-TaskTrigger -OnRegistration |             
    Register-ScheduledTask "DailyHotfixReport"                

Piece of cake, right?  In our case, we’re sending an email at 9 AM every day (and when the task is registered) with the output of a simple cmdlet, but I could also use this to run a more complex task, and I can play around with the different trigger types to send the emails whenever I’d like.

Here’s the full script:

 

New-Task |                        
    Add-TaskAction -Hidden -Script {                        
        $ErrorActionPreference = "Stop"                        
        try {                        
            $messageParameters = @{                        
                Subject = "Installed Program report for $env:ComputerName.$env:USERDNSDOMAIN - $((Get-Date).ToShortDateString())"                        
                Body = Get-WmiObject Win32_Product |                         
                    Select-Object Name, Version, Vendor |             
                    Sort-Object Name |             
                    ConvertTo-Html |                         
                    Out-String                        
                From = "Me@MyCompany.com"                        
                To = "Me@MyCompany.com"                        
                SmtpServer = "SmtpHost"                        
            }                        
            Send-MailMessage @messageParameters -BodyAsHtml                        
        } catch {                        
            $_ |                         
                Out-File $env:TEMP\ProblemsSendingHotfixReport.log.txt -Append -Width 1000                        
        }                        
    } |            
    Add-TaskTrigger -Daily -At "9:00 AM" |                        
    Add-TaskTrigger -OnRegistration |                         
    Register-ScheduledTask "DailyHotfixReport"

That wasn't bad, was it?  It took only 24 lines of PowerShell script to create a schedule task that emails me a fairly detailed report of all of the installed programs on the machine. As you can see, PowerShell V2 makes sending automated emails a snap, and the PowerShellPack makes scheduling this or any other script on a regular basis a piece of cake.

Hope this Helps,

James Brundage [MSFT]

Windows Management Framework is here!

Windows Management Framework, which includes Windows PowerShell 2.0, WinRM 2.0, and BITS 4.0, was officially released to the world this morning. By providing a consistent management interface across the various flavors of Windows, we are making our platform that much more attractive to deploy. IT Professionals can now easily manage their Windows XP, Windows Server 2003, Windows Vista, Windows Server 2008, Windows 7, and Windows Server 2008 R2 machines through PowerShell remoting – that’s a huge win!

You can download the packages here: http://go.microsoft.com/fwlink/?LinkID=151321

 

Lee Holmes [MSFT]

Tonight is the Virtual Launch Party @ PowerScripting Podcast

Tonight is the night! 

I was super happy when we shipped V1 of PowerShell.  It started the journey.  That said, V2 is the release that we really wanted to release. 

Today Windows 7 is publicly available and every version has PowerShell V2 in it. 

Tonight – we party!

I’ll be cracking a beer with Hal Rottenberg and Jonathan Walz (hosts of the PowerScripting Podcast) in a PowerShell V2 Virtual Launch Party tonight at 9:30 PM EDT (GMT-4). 

For more details and info on how to join us go HERE.  I’m looking forward to seeing you.  Note, you can always watch the podcast after the fact but if you join, you can make comments and ask questions in the chat room – lots of fun.

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

PowerShell V2 Virtual Launch Party

I’ll be joining Hal Rottenberg and Jonathan Walz (hosts of the PowerScripting Podcast) in a PowerShell V2 Virtual Launch Party this Thursday, Oct 22nd, 9:30 PM EDT (GMT-4). 

For more details and info on how to join us go HERE.

Hmmm, I wonder if it is BYOB… :-)

Experiment!  Enjoy!  Engage!

Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Announcing: Open Source PowerShell Cmdlet and Help Designer

http://www.codeplex.com/CmdletDesigner

During the development of Windows 7, most cmdlet design and help authoring in Microsoft went through an internal tool called the “Cmdlet Designer.”

cmdlet_designerThe Cmdlet Designer makes it much easier for teams to concentrate on the design, naming, and consistency of their cmdlets, while also guaranteeing name registration and collision avoidance across Microsoft.

To sweeten the deal, it offers:

  • Integrated help authoring
  • Efficient bulk operations (parameter and cmdlet cloning)
  • Generation of cmdlet code
  • Full scripting support
  • Automatic code-spec comparison and testing
  • Role-based security, history logging, and more.

So why blog about it? Because it’s now yours!

We just posted the entirety of the Cmdlet Designer, its source code, design specification, and deployment guide to http://www.codeplex.com/CmdletDesigner under the most permissive Microsoft Open Source license, the Microsoft Public License (MS-PL).

Architecturally, the Cmdlet Designer offers a reference implementation to benefit developers as well:

  • UI on top of Cmdlets
  • Script-based UI extensibility
  • Cmdlet / Webservice interaction
  • Role-based security, with a trusted subsystem implementation

Download, and enjoy!

 

Lee Holmes [MSFT]
Windows PowerShell Development
Microsoft Corporation

Introducing the Windows 7 Resource Kit PowerShell Pack

Since I work on the PowerShell team, I’ve been lucky enough to get a couple of years jump start on producing PowerShell V2 scripts and modules.  With every new script I write in V2 I get more and more amazed by the possibilities in PowerShell.  I’ve discovered how to do many cool things that, as some of my colleagues outside of the PowerShell team have put it, have showed them the Power of PowerShell.

At each CTP of PowerShell, I’ve tried to share this power of PowerShell here on this blog.  Thanks to Mitch Tulloch (the main author of the Windows 7 Resource Kit) and Ed Wilson (the Scripting Guy), I was able to collect some of my V2 modules for the companion CD of the resource kit.  This collection of scripts became know as the Windows 7 Resource Kit PowerShell Pack.

Today, the Windows 7 Resource Kit should be available in stores, and the PowerShell Pack is available for download on code gallery.

The PowerShell Pack contains over 800 scripts in 10 different modules.  Here’s a brief overview:

WPK
Create rich user interfaces quick and easily from Windows PowerShell. Think HTA, but easy. Over 600 scripts to help you build quick user interfaces.  To get started learning how to write rich WPF UIs in script, check out Writing User Interfaces with WPK.

IsePack
Supercharge your scripting in the Integrated Scripting Environment with over 35 shortcuts.

TaskScheduler
List scheduled tasks, create or delete tasks

FileSystem
Monitor files and folders, check for duplicate files, and check disk space

DotNet
Explore loaded types, find commands that can work with a type, and explore how you can use PowerShell, DotNet and COM together

PSImageTools
Convert, rotate, scale, and crop images and get image metadata

PSRSS
Harness the FeedStore from PowerShell

PSSystemTools
Get Operating System or Hardware Information

PSUserTools
Get the users on a system, check for elevation, and start-processaadministrator

PSCodeGen
Generates PowerShell scripts, C# code, and P/Invoke

I’ll be posting a lot more about these modules in the next weeks, both here and on Channel9, but you can start exploring the modules now by checking out the PowerShellPack on code gallery.  I will also be tweeting some cool tidbits about the PowerShell Pack under the tags: #PowerShellPack, #WPK and #IsePack

I’ll also be on Hal Rotenberg’s PowerScripting Podcast tonight talking about the PowerShell Pack.

Hope this helps,

James Brundage [MSFT]

Quick Note: The MSI was updated at ~ 4:30pm on day of release. If you downloaded it before this time, please download the MSI again.

Posted by PowerShellTeam | 13 Comments
Filed under: , ,
More Posts Next page »
 
Page view tracker