Tag Archives: Computername

[UPDATE: It's Safe] CAUTION: Don’t Run Update-Help Right Now


UPDATE 2 JULY 2013: Microsoft is informing MVPs that the fix is in, and new help files should be downloadable by (at latest) the morning of 3 July 2013. So get your Update-Help ready to run. More info.

If you haven’t recently run Update-Help… don’t. There’s a problem with the help files that have been produced recently so that instead of:

-computername <string[]>

You’re getting:

-computername

This affects all parameters – no value types will be shown. This has been reported to Microsoft, and they’ve acknowledged receipt of that report and are investigating. Personally, I believe the problem may be related to internal-use-only tools that are used to create the syntax section of the help files, so hopefully it’ll be an easy fix.

The -full and -detail help still shows the correct information, so if you’ve downloaded the borked help files, you’re not totally out of luck.

As far as I can determine, this only currently affects core PowerShell cmdlets, not add-in modules from product teams like Exchange, etc. I believe that’s because the core cmdlets were just updated and re-published, something the PowerShell team tends to do a bit more frequently than some of the other product groups.

I’ll keep you posted as I learn anything new.

Some notes on Event 2 Advanced


I hate to seem negative, but I’ve noticed a few things about a number of the advanced entries that seem like folks didn’t read the instructions, or just weren’t careful about details.

There were a surprising number of entries that had [string]$ComputerName instead of [string[]]$ComputerName in the params section and then went on to treat the parameter as if it were an array.

  • Somewhat related to the array issue, the problem statement indicated that there could be several files that had computer identification for piping into the solution. Several scripts went beyond the minimum by accepting a filename property to process those files directly. I don’t think that extension is out-of-bounds, but  scripts that accepted only filenames and excluded ComputerName input didn’t get my vote.
  • The instructions asked for a “full help display”, but many of the entries had fairly limited documentation. One thing I especially missed was a .PARAMETER description.
  • My last negative comment is about parameter names. Although there’s nothing in PowerShell to prevent it, best practices in parameter names should be followed. The parameter ought to be $ComputerName, not $Name, $Server, $Computer, etc. I know it’s easier with verbs and nouns because of the Get-Verb and Get-Noun cmdlets, but please pay attention to how you name your parameters.

On the whole, though I really liked the effort everyone put into their scripts. Those that exactly met the requirements were short, sweet, and to the point. There were several extensions that I also liked.

  • Working with optional credentials. It was reasonable to assume that the script would be run using appropriate credentials, some of the scripts accepted alternate credentials for making the CIM or WMI queries. I consider it a best practice to log in and execute tasks at low permissions levels (standard user) and to use elevated credentials only on the specific commands that need them. Kudos also to those of you who accepted either a credentials object or a user name and found the credentials.
  • Using parallel execution to speed up the process. PowerShell provides runspaces, workspaces, and jobs to allow multiple commands to execute concurrently. Nothing in the event hinted at using parallelism, so I put these on my “clever” list.
  • Using PowerShell 3’s CIM cmdlets. Using the new features of the latest version of PowerShell is quite good, especially when making use of the backwards compatibility features. I would have done this a bit differently than most, though. Instead of always using the Dcom session option, I would have opened a SimSession using a try {WSMAN} Catch {DCOM} and running the queries against the session.

So, good work, everybody. Let’s see what more we can learn in event 3.

Scripting Games Week 2: Formatting edition


This time of the year always feels like someone is holding down the fast forward button.  I blinked and here we are Friday morning another week of scripts in the rear view.  I spent most of my week in the beginner class this week, and was greeted by a combination of beginners and scripters who weren’t quite ready to step up to advanced.  More of the latter if I’m to be honest.  This was a pleasant surprise as it’s another sign of the continuing growth of our community.  Now on to the scripts I knew when I signed up to do this, that at least one of these weeks I’d talk about formatting.  It’s one of those best practices that you don’t appreciate until you’re asked to review someone else’s code.

Don’t Crunch the Code, and for the love of all things, Hit Enter!
I did not deduct any points for readability, but you didn’t make my good list either.  Personally I find it disrespectful to share an ungodly one-liner, but it’s downright wrong if that single line has semicolons!  We’re not printing these scripts the crunch gets us nothing. I’m not going to call out the litany of scripts that were manually formatting the data directly which is even worse, but consider the following.

Get-Content C:\IpList.txt | Foreach-Object { $Processor = Get-WmiObject -ComputerName $_ -NameSpace "Root\CIMV2" -Class "Win32_Processor"; $OpSystem  = Get-WmiObject -ComputerName $_ -Namespace "Root\CIMV2" -Class "Win32_OperatingSystem"; New-Object -TypeName PSObject -Property @{ Name = $Processor.SystemName; Cores = $Processor.NumberOfCores; OS = $OpSystem.Caption; Version = $OpSystem.Version; Memory = $OpSystem.TotalVisibleMemorySize }  }

This is an almost perfect solution and it’s utilizing my next tip for this week already, but the formatting made it unnecessarily hard to read. Let just clean this up a bit by inserting a proper CR in place of all those semi-colons.

Get-Content C:\IpList.txt | Foreach-Object { 
    $Processor = Get-WmiObject -ComputerName $_ -Class "Win32_Processor"
    $OpSystem  = Get-WmiObject -ComputerName $_ -Class "Win32_OperatingSystem"
    New-Object -TypeName PSObject -Property @{ 
        "Name"    = $Processor.SystemName
        "Cores"   = $Processor.NumberOfCores
        "OS"      = $OpSystem.Caption
        "Version" = $OpSystem.Version
        "Memory"  = $OpSystem.TotalVisibleMemorySize 
    }  
}

Does anyone honestly not think the latter is better? The whitespace cost nothing at execution, and makes it an order of magnitude easier for a human being to read, process, and comprehend! I don’t care what you do in your own scripts but when another human being is going to be asked to read it take a moment and format it. By the way for those in audience in love with the all-powerful one-liner both those examples are one-liners.
That’s Sooooo 2006!
Seriously, it’s okay to use the latest features of the language! Heck how about we just agree to use the features from the last version! What am I talking about? object creation! Again I didn’t take any points off for this, and you may have made my good list, but I didn’t like it. Select-Object and Add-Member NoteProperty were how we built custom object in 2006 with PowerShell v1. PowerShell V2 added an extremely powerful –Property parameter to New-Object that completely removed the need for Add-Member, and PowerShell V3 introduced the [PSCustomObject] type accelerator that removed them all! Consider the following look back at the past six years of PowerShell Object Creation.

# 2006
Get-Content .\IpList.txt | Foreach-Object { 
    $Processor = Get-WmiObject -ComputerName $_ -Class "Win32_Processor"
    $OpSystem  = Get-WmiObject -ComputerName $_ -Class "Win32_OperatingSystem"
    New-Object -TypeName PSObject |
        Add-Member -MemberType Noteproperty -Name "Name" -value $Processor.SystemName -PassThru | 
        Add-Member -MemberType Noteproperty -Name "Cores" -value $Processor.NumberOfCores -PassThru | 
        Add-Member -MemberType Noteproperty -Name "OS" -value $OpSystem.Caption -PassThru | 
        Add-Member -MemberType Noteproperty -Name "Version" -value $OpSystem.Version -PassThru |
        Add-Member -MemberType Noteproperty -Name "Memory" -value $OpSystem.TotalVisibleMemorySize -PassThru
} 

# 2007 This worked in 2006, but it took a little while to catch on.
Get-Content .\IpList.txt | Foreach-Object { 
    $OpSystem  = Get-WmiObject -ComputerName $_ -Class "Win32_OperatingSystem"
    Get-WmiObject -ComputerName $_ -Class "Win32_Processor"|  
        Select-Object -Property SystemName, NumberOfCores,
            @{'Name'="OS";"Expression"={$OpSystem.Caption}},
            @{'Name'="Version";"Expression"={$OpSystem.Version}},
            @{'Name'="Memory";"Expression"={$OpSystem.TotalVisibleMemorySize}}
} 

# 2009
Get-Content .\IpList.txt | Foreach-Object { 
    $Processor = Get-WmiObject -ComputerName $_ -Class "Win32_Processor"
    $OpSystem  = Get-WmiObject -ComputerName $_ -Class "Win32_OperatingSystem"
    New-Object -TypeName PSObject -Property @{
        "Name"   = $Processor.SystemName
        "Cores"  = $Processor.NumberOfCores
        "OS"     = $OpSystem.Caption
        "Version"= $OpSystem.Version
        "Memory" = $OpSystem.TotalVisibleMemorySize
    }
} 

# 2012
Get-Content .\IpList.txt | Foreach-Object { 
    $Processor = Get-WmiObject -ComputerName $_ -Class "Win32_Processor"
    $OpSystem  = Get-WmiObject -ComputerName $_ -Class "Win32_OperatingSystem"
    [PSCustomObject]@{
        "Name"   = $Processor.SystemName
        "Cores"  = $Processor.NumberOfCores
        "OS"     = $OpSystem.Caption
        "Version"= $OpSystem.Version
        "Memory" = $OpSystem.TotalVisibleMemorySize
    }
}

They are all more or less the same. When properly formatted they are all equally readable. Most of them use a hash table of some sort. Therefor there are some language hurdles that need to be cleared, so why bother, why does it matter?… simple performance, with every release the PowerShell team have refined Object creation and the new way is always just a little bit faster. I used measure-command to measure the execution times for the above examples and well as you can see while minute every subsequent technique is slightly faster.

New-Object/Add-Member = 1128 Milliseconds
Select-Object                    = 1114 Milliseconds
New-Object –property      = 1107 Milliseconds
PSCustomObject             = 1100 Milliseconds

Again not a huge deal but given a large enough dataset every tick counts. There were a litany of other things that I saw this week that made my list. The good news is this is all nitpicky stuff which is awesome!   Keep it up, and for the rest of you voters out there lets ease up with the ones and twos these are awesome scripts.  They may not use the technique you’d prefer but for the most part they’re getting the job done.

~Glenn

Notes on Beginner Event 2


 First of all, congratulations! It looks to me like a lot of learning is going on; the 2nd event entries look really good to me. I especially liked the way a number of you built up a one-liner by starting with a Get-WmiObject Win32_ComputerSystem -ComputerName (Get-Content file.txt) and piping it into Select-Object to generate the data. However, there were a couple of areas within the Select block that make me think that some more discussion of what $_ means in a pipeline would be helpful.

Within the Select block, it is necessary to make a call to Get-WmiObject Win32_OperatingSystem to get come additional information. It looks like everybody got the format correct: @{Name=’OS';Expression={Get-WmiObject}} where folks got into trouble was in specifying the ComputerName property. Some didn’t even include it, meaning that the OS value would be taken from the local computer and not the remote one. But, more often than not, the code contained a plain $_ : @{Name=’OS';Expression={(Get-WmiObject Win32_OperatingSystem -ComputerName $_).Caption}}. So, what’s wrong with this? The problem is the value of $_ at this point in the pipeline.

Let’s try an experiment to show what I mean. Try this:

Get-WmiObject Win32_ComputerSystem | Select-Object @{Name='OS';Expression={Get-WmiObject Win32_OperatingSystem -ComputerName $_}}

 

What does it return? Only the label “OS” with no data and no error message. Why? To find out, lets change the code a little and see.

Get-WmiObject Win32_ComputerSystem | foreach {Get-WmiObject Win32_OperatingSystem -ComputerName $_}

This time, we do get an error message:

Get-WmiObject : Invalid parameter At line:1 char:47 + Get-WmiObject Win32_ComputerSystem | foreach {Get-WmiObject Win32_OperatingSyste ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

 “Invalid Parameter” means that $_ isn’t a computer name. What is it? It’s actually the entire Win32_ComputerSystem object. What you need to do is to select one of the object properties that contains the system’s name ($_.__SERVER, $_.Name, or $_.PSComputerName).

Hopefully, this wasn’t too long or complex a description. The point is be careful in your pipelines that you know exactly what $_ means at each step.

 

 

Don’s Event 2 Notes


I thought I’d mentioned this last time (tap tap, this thing on?), but maybe not: don’t format the output of your functions. The minute a function includes Format-*, you’ve trapped me into on-screen display, a text file or piece of paper modeled after the on-screen display, or not a lot of other choices. If I want formatting, I’ll pipe your function to my own Format-* command of choice. But if I want CSV, or HTML, or XML, I’d like that option. Thanks.

This is not a favorite technique of mine:

 $ServerInfo = "" | Select-Object Name, SerialNumber, OS, Model, CPU, CPUCount, Memory, GBMemory
$ServerInfo.Name = $Server.ToUpper()
$ServerInfo.SerialNumber =(Get-WmiObject -Class Win32_BIOS -ComputerName $Server -Credential $Credential).SerialNumber

That said, it’s not “wrong” so I only knock of like 1/10th of a point. For me, this technique is a bit of a hack, and it doesn’t parse well visually. You’re relying on Select-Object accepting non-existent property names and turning them into blank properties for you. It’s… well, it’s weird, and frankly this behavior – while convenient in this instance – causes more harm than good. Ever typo a property name on Select, and get a blank column as a result? Yeah, that. I wish Select didn’t work this way, and so as a result I’m not a fan of this technique.

if ($ServerInfo.CPU -is [array]) {
  $ServerInfo.CPU = $ServerInfo.CPU[0]
}

Nice thinking, muchacho. You don’t know if you’ve got more than one object, so you check. I’ll note, however, that this could have been done more concisely when you got the property:

$ServerInfo.CPU = (Get-WmiObject -Class Win32_Processor -ComputerName $Server -Credential $Credential).Name

Add a Select -First 1 to the end of that and you’d be guaranteed of only having one.

 $ServerInfo.SerialNumber =(Get-WmiObject -Class Win32_BIOS -ComputerName $Server -Credential $Credential).SerialNumber
$ServerInfo.OS = (Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Server -Credential $Credential).Caption
$ServerInfo.Model = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Server -Credential $Credential).Model
$ServerInfo.CPU = (Get-WmiObject -Class Win32_Processor -ComputerName $Server -Credential $Credential).Name
$ServerInfo.CPUCount = (Get-WmiObject -Class Win32_Processor -ComputerName $Server -Credential $Credential).count
$ServerInfo.Memory = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Server -Credential $Credential).TotalPhysicalMemory

Saw a lotta this. I’m kinda picking examples from one script, but this happened a lot. You’re executing 6 queries. You needed 3. Double the effort, double the time. Bad call. Query it once, save it in a variable, extract what you need from that.

param(
 [Parameter(
  Position=0,
  Mandatory=$true,
  ValueFromPipeline=$true,
  ValueFromPipelineByPropertyName=$true
 )]
 [string[]]$computers
)

This hurts a little. Look at every native PowerShell command that accepts computer names, and it does so on a -ComputerName parameter. So why pick -computers for your function and be all nonstandard? Stay consistent.

 $s = New-Object System.Object

$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer
$s | Add-Member -Type NoteProperty -Name "Server Name" -Value $os.CSName
$s | Add-Member -Type NoteProperty -Name "OS Version" -Value $os.Caption

$cs = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer

$mem = [string]([Math]::Round(($cs.TotalPhysicalMemory / 1MB),2)) + " MB"
$s | Add-Member -Type NoteProperty -Name "PhysicalMem" -Value $mem
$s | Add-Member -Type NoteProperty -Name "# CPUs" -Value $cs.NumberOfProcessors

$cpu = Get-WmiObject -Class Win32_Processor -ComputerName $computer

Ahh, that’s better. One query per class, then extract what you want from a variable. You can be a bit more concise using a hashtable, but I’m jiggy with this technique.

I said “jiggy.”

I want to point out that Dr. Scripto was optional about the “number of cores in each socket” thing. He said, “if you can do it.” You can’t. Not readily; XP doesn’t expose that information (having existed before the advent of cores, um, time to upgrade okaythanksbuhbye) so you couldn’t get it consistently for all of the operating systems you were asked for. Sometimes, the test is about seeing when you know to quit, not seeing if you can piledrive your way into a half-answer.

You know you totally get downvoted if you don’t include comment-based help with functions, right? Advanced track only. Just saying.—

"Server name: " + $Info.Caption
"OS: " + $Info2.Caption + $Info2.CSDVersion
"Processor sockets: " + $Info.NumberOfProcessors
"Processor cores: " + $Info.NumberOfLogicalProcessors
"Physical memory: " + [Math]::Round(($Info.TotalPhysicalMemory/1GB),2) + "GB"

Yeah. Outputting formatted text instead of objects. I know. I cried for the dead puppies, and then drank. I drank vodka. I hate vodka, but the puppies. There is seriously a better way to output – outputting text prevents PowerShell from doing ANYTHING USEFUL with your output. See how this guy did it? Do that. I’m not a huge ordered hashtable fan, but that’s just me. I don’t hate them as much as vodka. Or text output.

This one is trending well. I get it. It’s beautiful. I think I wrote a book about this. My ONLY SINGLE NITPICK is that it’s maybe a wee bit overwrought. I think it’s because of the whole try CIM, then try DCOM, thing. He probably had to do it this way. I wish the new CIM cmdlets didn’t require an explicit session to do DCOM. I think that’s a big fail, because it forces you to write functions like this. Meh. I should write a proxy function for this. Anyway.

 Write-Verbose -Message 'Creating runspace pool'
$rp = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThrottleLimit, $iss, $Host)
$rp.Open()

I have no idea what to do with this. Here’s the whole thing. This person is likely a LOT smarter than me. Certainly WAY more patient. I’m not sure Dr. Scripto anticipated a 317-line solution. I think he’s rolled his own multithreading here. Just… wow. It’s definitely overkill, by an order of magnitude, but props, man.

Someone can explain it to me sometime after the vodka wears off, yeah?

People are hating on this one. They’re wrong. It’s a good entry. Let me tell you something, stop giving a score of “2” because someone did something extra like add logging. If it works, they went above and beyond. Do you not reward people for going above and beyond in your organization? No? Well, you should.

Find Files with PowerShell 3.0


My last few articles have looked at using WMI and CIM_DATAFILE class to find files, primarily using Get-WmiObject in PowerShell. But now that we have PowerShell 3.0 at our disposal, we can use the new CIM cmdlets. So I took my most recent version of Get-CIMFile and revised it specifically to use Get-CimInstance. I also took the liberty of adding a few more refinements, some of which you could integrate into previous versions. Here’s the new v3 function.

Function Get-CIMFile {
#comment basedhelp

[cmdletbinding(DefaultParameterSetName="Computername")]

Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidatePattern("^[a-zA-Z]:$")]
[string]$Drive="C:",
[Parameter(ParameterSetName="Computername")]
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[Parameter(ParameterSetName="CIMSession")]
[ValidateNotNullorEmpty()]
[Microsoft.Management.Infrastructure.CimSession[]]$CimSession
)

Write-Verbose "Starting $($MyInvocation.MyCommand)"
Write-Verbose "Parameter set = $($PSCmdlet.ParameterSetName)"

#create a hashtable of parameter values that can be splatted to Get-CimInstance
$paramHash=@{Classname="CIM_DATAFILE"}

Write-Verbose "Searching for $filename on drive $drive"
if ($pscmdlet.ParameterSetName -eq "Computername") {
    Write-Verbose "…on $computername"
    $paramHash.Add("Computername",$computername)
}
elseif ($pscmdlet.ParameterSetName -eq "CimSession")  {
    Write-Verbose "…on $Cimsession"
    $paramHash.Add("CimSession",$cimSession)
}
else {
    #this should never happen
    Write-Verbose "No computername or cimsession specified. Defaulting to local host"
    #bail out of the function
    Return
}

#define default operators
$fileOp="="
$extOp="="

<#
Normally you might think to simply split the name on the . character. But
you might have a filename like myfile.v2.dll so that won’t work. In a case
like this the extension would be everything after the last . and the filename
everything before.

So instead I’ll use the substring method to "split" the filename string.
#>

#get the index of the last .
$index = $name.LastIndexOf(".")

#it is possible the filename doesn’t have an extension
if ($index -gt 0) {
    #get the first part of the name
    $filename=$Name.Substring(0,$index)
    #get the last part of the name
    $extension=$name.Substring($index+1)
}
else {
    $filename=$Name
    #will need to use wildcard search for filename when extension is empty
    $fileop="LIKE"
    $extension=$null
}
#if there is * in the filename or extension, replace it with %
#and change the comparison operator for the WMI query
if ($filename -match "\*" ) {
    Write-Verbose "Wildcard search on filename"
    $filename = $filename.Replace("*","%")
    $fileOp="LIKE"
}

if ($extension -match "\*") {
    Write-Verbose "Wildcard search on extension"
    $extension = $extension.Replace("*","%")
    $extOp="LIKE"
}

$filter = "Filename $fileOp ‘$filename’ AND extension $extOp ‘$extension’ AND Drive=’$drive’"
Write-Verbose $filter

#add the filter to the hashtable
$paramHash.Add("Filter",$filter)

#invoke the command
Write-Verbose "Parameter Hash $($paramHash| out-String)"

#let’s time how long it took
$start=Get-Date  

Get-CimInstance @paramhash

$end=Get-Date
Write-Verbose "Search completed in $($end-$start)"
Write-Verbose "Ending $($MyInvocation.MyCommand)"

} #end Get-CIMFile

This version let’s you search remote computers by name or CIM session. Because they are mutually exclusive options, this function uses parameter sets, defaulting to using a computername. I also added a validation check on the drive name using regular expressions. The function will fail if the value is not a letter followed by a colon.

Another major change was modifying code to search for filenames without an extension. What if you are looking for a file like README? The WQL query turned out to be more complicated than I imagined. It would be easier if the extension property was NULL, but it isn’t. It is a 0 length string. I found that in order to make this work, I needed to create a query like this:

SELECT * FROM CIM_DATAFILE WHERE Filename LIKE ‘readme’ AND extension = ” AND Drive=’d:’

So I modified my code to adjust operators and variables that I use to build the filter string.

#get the index of the last .
$index = $name.LastIndexOf(".")

#it is possible the filename doesn’t have an extension
if ($index -gt 0) {
    #get the first part of the name
    $filename=$Name.Substring(0,$index)
    #get the last part of the name
    $extension=$name.Substring($index+1)
}
else {
    $filename=$Name
    #will need to use wildcard search for filename when extension is empty
    $fileop="LIKE"
    $extension=$null
}

$filter = "Filename $fileOp ‘$filename’ AND extension $extOp ‘$extension’ AND Drive=’$drive’"

Now I can find files without an extension, or with.

PS C:\>  get-cimfile readme -drive d:

Compressed     : False
Encrypted      : False
Size           :
Hidden         : False
Name           : d:\readme
Readable       : True
System         : False
Version        :
Writeable      : True
PSComputerName : SERENITY

PS C:\>  get-cimfile readme.txt -drive d:

Compressed     : False
Encrypted      : False
Size           :
Hidden         : False
Name           : d:\readme.txt
Readable       : True
System         : False
Version        :
Writeable      : True
PSComputerName : SERENITY

The last major change you’ll notice is that I build a hash table of parameter values and then splat it.

$paramHash=@{Classname="CIM_DATAFILE"}

Write-Verbose "Searching for $filename on drive $drive"
if ($pscmdlet.ParameterSetName -eq "Computername") {
    Write-Verbose "…on $computername"
    $paramHash.Add("Computername",$computername)
}
elseif ($pscmdlet.ParameterSetName -eq "CimSession")  {
    Write-Verbose "…on $Cimsession"
    $paramHash.Add("CimSession",$cimSession)
}

#add the filter to the hashtable
$paramHash.Add("Filter",$filter)

Get-CimInstance @paramhash

This is a terrific technique when you are dynamically generating parameters.

Get-CimInstance can be used to query remote computers, assuming they are also running PowerShell 3.0. However, you can also use CIM Sessions which allow you to establish a connection to an older system using the DCOM protocol.

$sess = New-CimSession jdhit-dc01 -SessionOption (New-CimSessionOption -Protocol Dcom)

The end result is that I can still use my PowerShell 3.0 function to query a PowerShell 2.0 machine as long as I have a pre-created session.

get-cimfile3

Now I have a very powerful tool that can search just about any computer in my domain.

Oh, one more thing I realized in working on this. Initially I was only paying attention to the file name and version. Then I noticed that the Size property in the default output was always empty. That struck me as odd and not very useful. So I looked at the actual object with Get-Member and it turns out there is a FileSize property which is populated. It looks like the default property set for CIM_DATAFILE uses the Size property, when it should really be FileSize. So keep that in mind as you are working with the results.

Download Get-CIMFile3 and try it out for yourself.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Piping between functions


A question came up about piping between advanced functions. The input to the second function might be an array. To illustrate how this works imagine a function that gets disk information – or better still use this one.

function get-mydisk{             
[CmdletBinding()]             
param (             
   [string]$computername="$env:COMPUTERNAME"             
)             
BEGIN{}#begin             
PROCESS{            
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername |            
foreach {            
New-Object -TypeName PSObject -Property @{            
 Disk = $_.DeviceID            
 Free = $_.FreeSpace            
 Size = $_.Size            
}            
}            
}#process             
END{}#end            
}

Use a computername as a parameter. Use WMI to get the disk information and output an object.

PS> get-mydisk | ft -AutoSize

Disk         Free         Size
—-         —-         —-
C:   149778239488 249951154176
D:       69271552    104853504
E:                           
F:                           

This works as well

 

PS> get-mydisk | where Size -gt 0 | ft -AutoSize

Disk         Free         Size
—-         —-         —-
C:   149778108416 249951154176
D:       69271552    104853504

You now have a function outputs objects that behave properly on the pipeline.

So now you want those objects piped into another function or you want an array of objects used as the input

function get-freeperc {             
[CmdletBinding()]             
param (             
[parameter(ValueFromPipeline=$true)]            
  [Object[]]$disklist             
)             
BEGIN{}#begin             
PROCESS{            
            
foreach ($disk in $disklist){            
 if ($disk.Size -gt 0){            
   $disk | Select Disk,            
   @{N="Size(GB)"; E={[math]::Round( ($($_.Size)/1GB), 2 )}},            
   @{N="FreePerc"; E={[math]::Round( ($($_.Free) / $($_.Size))*100, 2 )}}            
 }            
}            
            
}#process             
END{}#end            
}
  • Set the parameter to accept pipeline input
  • Set the parameter to accept an array of objects
  • Use a process block
  • Use a foreach block in the process block

This works

PS> get-mydisk | get-freeperc | ft -AutoSize

Disk Size(GB) FreePerc
—- ——– ——–
C:     232.79    59.92
D:        0.1    66.07

or this

$disks = get-mydisk
get-freeperc -disklist $disks

or this

get-freeperc -disklist (get-mydisk)

Account SIDs


A question on the forum asked about finding the accounts and SIDs on the local machine.

function get-SID {            
param (            
 [string]$computername = $env:COMPUTERNAME            
)            
            
Get-WmiObject -Class Win32_AccountSID -ComputerName $computername |            
foreach {            
 $da =  (($_.Element).Split(".")[1]).Split(",")            
 $sid = ($_.Setting -split "=")[1] -replace '"',''            
            
 $props = [ordered]@{            
 Domain = ($da[0] -split "=")[1] -replace '"',''            
 Account = ($da[1] -split "=")[1] -replace '"',''            
 SID = $sid            
 }            
             
 New-Object -TypeName PSObject -Property $props            
}            
            
}

Pass a computer name into the function – default is local machine.

Use the AccountSID class which links Win32_SystemAccount and Win32_SID.  For each returned instance clean up the data and create an object with three properties – domain, account and SID.

You will see more than you thought – some very useful information buried in there