Posts RSS Comments RSS 253 Posts and 411 Comments till now

Archive for the 'Custom Objects' Category

Calculated Properties

nicad49 asked
“Can you explain the last part of each script? Specifically the @{n=’Servers’;…}etc. I understand that we are creating some sort
of an array, but if I run it as is, I don’t get any values returned.”

This is great question. I see examples like this used all the time without explanation. Unless you had the chance to review all the
help files from all the different CMDLets there is good chance you missed some REALLY cool stuff. This is one of those things
hidden in the bowels of Help files and Books.

Before I go into my specific example, lets look at this excerp from http://technet.microsoft.com/en-us/library/bb978655.aspx

“You can also use Select-Object to add calculated properties to an object. To add a calculated property, use the Property
parameter and type a hash table for the parameter value. Create an Expression key in the hash table and assign to the key an
expression that calculates a value. The hash table can also have a Name key.”

Basically what we can do is add a property to an object that is calculated. This calculation could be from other properties or just a
change to the way the data is configured. Here is how you use this feature @{name=”SomeName”;expression={“Script block used
to provide value(s)”}}.

Some important things to note:
* The Name can be anything, even a valid property name.
* Expression is a scriptblock. What does this mean? It means that you can put any code you want in there… even an entire script
and what ever is returned is put in the property you created.

Now that we have some foundation lets look back at my code. Here is the first example.

$MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
$MF.Initialize(1)
$MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
  @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType

Now the first problem maybe cut/paste not working correct so make sure line three is all one big long line.

Next lets zoom in on the Important line… number three

$MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
  @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType

First we have the $MF.Servers. This returns an array of Citrix Server Objects with all sorts of properties. We only really care about
the Applications and the Server name so we go to the next step.

Select-Object ServerName -expand Applications. This code takes each one of the MFCom Server objects and Strips out everything
but the ServerName and the Applications. The reason for -expand is because Applications is an array of MFCom Application
Objects and we want them all.

Here is the biggy
Select-Object ServerName,AppName,DistinguishedName,@{n=’Users’;e={$_.Users | %{$_.UserName}}},@{n=’Groups’;e={$_.Groups | %{$_.GroupName}}}
This section takes the object we Created with the ServerName and the Applications and Passes on the ServerName, but extracts
information about the applications we want to know like AppName and DistinguishedName. The problem is that we want the users
and groups to. The problem with this is these properties are (like Server and Application) arrays of MFCom User or MFCom Group
objects. Enter calculated fields.

Lets look each calculated field
@{n=’Users’;e={$_.Users | %{$_.UserName}}}
This basically says create a property named Users and add the values from $_.Users, but only give me the UserName… not all the
other properties.
@{n=’Groups’;e={$_.Groups | %{$_.GroupName}}}
Same thing except for with Groups.

Perhaps multilevel Citrix Nesting isn’t the best place to get your teeth wet on calculated properties, but I hope I cleared it up a bit
for you.

Get-CitrixServerLoad (The power of objects in Citrix)

I watch the forums at BrianMadden.com because I use Powershell a lot for Citrix. This question was brought up.

Q: How could one:
– query “server load” on all servers part of the farm
– extract all server under a minimum server load
– apply an “Offline” load evaluator on the extracted servers (in order to make them unavailable on the farm)

I posted a script to do what they wanted, but then I got to thinking… while it did achieve the goal it wasn’t very Powershellish.

As I have said over and over. The glory of Powershell is the objects. So I decided to Post this entry showing what I would consider the Powershell way 🙂

Ideally you should just do this at the prompt

PS> Get-CitrixServers | where{$_.WinServerObject.Serverload -lt $load} | Set-CitrixLoadEvalutor “OffLine”

This is easy to achieve with the following scripts or even better make them functions!

Get-CitrixServers

param($Server)
$type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
$mfarm = [system.Activator]::CreateInstance($type)
$mfarm.Initialize(1)
$mfarm.zones | foreach-Object{$_.OnlineServers}

Set-CitrixLoadEvalutor

Param($server,$LoadEvaluator = "MFDefaultLE",[switch]$Verbose)
#NOTE: This only work for 4.0 and 4.5
if($verbose){$verbosepreference = "Continue"}

function Set-LE{
    Param($mySrv)
    # Getting Current LE
    write-Verbose "   + Set-LE called : $($mySrv.ServerName)"
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "     – Old Evaluator: $($le.LEName)"
    Write-Verbose "     – Setting to $LoadEvaluator"

    # Assigning New LE
    $mySrv.AttachLEByName($LoadEvaluator)

    # Checking LE
    $le = $mySrv.AttachedLE
    $le.LoadData(1)
    Write-Verbose "     – Load Evaluator Set to $($le.LEName)"

}

if($Server)
{
    # Loading Server Object
    Write-Verbose " + Processing $Server"
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
    $mfServer = [system.Activator]::CreateInstance($type)
    $mfServer.Initialize(6,$Server)
    Write-Verbose "   – Calling Set-LE"
    Set-LE $mfServer
}

if($list)
{
    foreach($Srv in (Get-Content $list))
    {
        Write-Verbose " + Processing $Srv"
        # Loading Server Object
        Write-Verbose "   – Getting Citrix Object"
        $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Srv)
        $mfServer = [system.Activator]::CreateInstance($type)
        $mfServer.Initialize(6,$Srv)
        Write-Verbose "   – Calling Set-LE"
        Set-LE $mfServer
    }
}

if($input)
{
    foreach($Srv in $input)
    {
        Write-Verbose     " + Processing $Srv"
        if($Srv.ServerName)
        {
            Write-Verbose "   – Input is a Citrix Server: $Srv"
            Write-Verbose "   – Calling Set-LE"
            Set-LE $Srv
        }
        else
        {
            Write-Verbose "   – Input: $Srv"
            # Loading Server Object
            Write-Verbose "   – Getting Citrix Object"
            $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Srv)
            $mfServer = [system.Activator]::CreateInstance($type)
            $mfServer.Initialize(6,$Srv)
            Write-Verbose "   – Calling Set-LE"
            Set-LE $mfServer
        }
    }
}

This was the all in one that I posted

Param($Server,$minLoad = 1000,$LoadEval,[switch]$verbose)
if($verbose){$verbosepreference = "continue"}
function Get-CitrixFarm{
    param($Srv)
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Srv)
    $mfarm = [system.Activator]::CreateInstance($type)
    $mfarm.Initialize(1)
    Write-Verbose "Loading Farm $($mFarm.FarmName)"
    return $mFarm
}
function Set-CitrixLoadEvalutor{
    Param($server,$LoadEvaluator = "MFDefaultLE")

    # Loading Server Object
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
    $mfServer = [system.Activator]::CreateInstance($type)
    $mfServer.Initialize(6,$Server)

    # Getting Current LE
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "Old Evaluator: $($le.LEName)"
    Write-Verbose "Setting Load Evaluator on $server to $LoadEvaluator"

    # Assigning New LE
    $mfServer.AttachLEByName($LoadEvaluator)

    # Checking LE
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "Load Evaluator Set to $($le.LEName)"
}

$farm = Get-CitrixFarm $Server
foreach($ctxServer in $farm.Servers)
{
    $load = $ctxServer.WinServerObject.Serverload
    Write-Host ("{0,-15} :: {1}" -f $ctxServer.ServerName,$load)
    if($load -lt $minLoad)
    {
        Write-Verbose "Setting Offline Load Eval"
        if($LoadEval){Set-CitrixLoadEvalutor $ctxServer.ServerName $LoadEval}
    }
}

Using Switch -RegEx to create Custom Object (Getting HBA Info)

The other day I had a need to collect HBA (Host Base Fiber Adapters) from all my Servers. So the first place I looked was at WMI, but unfortunately… no dice. It didnt have the information I needed. The only way I knew to get the info I needed was to use was HBACmd.exe (utility to collect HBA information remotely.) So I went to writing a wrapper script in powershell to call the exe and then grep the text for what I was looking for, but I thought… HEY! Thats not the powershell thing to do! We do objects not text. So I went to parsing the text and making an object out of it. The script below is the result and while I dont believe many of you will find it particularly useful as it has a VERY specific use, I wanted to share with you how I went about objectizing the output.

Here is a Little Q and A on the script

Q: Purpose?
A: To get all the HBA information include Type, Firmware, Bios, Target Lun, WWN and a slew of other stuff.

Q: Why Did I objectize my text?
A: Because I know can simply use properties to filter and output data. Like what Machines have what Bios and what type of HBAs they have. Before I would have the parse the text for every different senario… now I just use where-object and filter away.

I few things that I wanted to point out here are the use of Switch to create the Custom Objects. IMO, Switch is one of the most powerful commands in the Powershell Language. It is INSANELY Powerfull. To be honest, It is pretty much the only one you need.

To compare it “Select Case” in vbscript would be insulting, but it can peform a similar function Like

switch ($a){
     Value1    {"It was Value 1"}
     Value2    {"It was Value 2"}
     Value3    {"It was Value 3"}
     Value4    {"It was Value 4"}
}

It would take a whole series of post to completely cover switch, but for this one I only want to go over -regex use. For complete use read the help located:
PS> Get-help About_Switch # Read it, Learn it, Love it

Some Quick Notes about Switch
– Can use RegEx, WildCard, Exact, CaseSensitive, or File options.
– It takes input via Pipeline {expression} or File. The cool thing is the pipeline can be any expression that results in piped output.
– For each match it can perform any ScritpBlock use $_ as the current Item
– It performs EVERY match on each element unless you use Continue after a match to stop processing that record

Like I said, Switch is insanely powerful. Just one of those powers is using RegEx for comparison.

Here is an example of using the -RegEx option

switch -RegEx (Get-ChildItem C:\test)
{
    "^\d"                  {"Starts with number:          " + $_.FullName}
    "\d"                   {"Has a number in it:          " + $_.FullName}
    "[^A-Za-z]"            {"Does NOT start with Number:  " + $_.FullName}
    "tmp"                  {"Has ‘tmp’ in it:             " + $_.FullName}
    # Notice that you can even use and expression to match
    {$_.Mode -match "-a"}  {"Has Archive Bit Set:         " + $_.FullName}
}

Now.. lets look at the script below. You will noticed I used RegEx to decide what value gets put in to what property of the object.

The script converts the output of three commands into two different objects. Lets look at one of them

It takes text like This

Manageable HBA List

Port WWN   : 10:00:00:00:11:11:11:11
Node WWN   : 20:00:00:00:11:11:11:11
Fabric Name: 00:00:00:00:00:00:00:00
Flags      : 0000f0a5
Host Name  : Server1
Mfg        : Emulex Corporation

Port WWN   : 10:00:00:00:22:22:22:22
Node WWN   : 20:00:00:00:22:22:22:22
Fabric Name: 00:00:00:00:00:00:00:00
Flags      : 0000f0a5
Host Name  : Server1
Mfg        : Emulex Corporation

And converts Into an object like This

  TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
—-        ———-   ———-
Equals      Method       System.Boolean Equals(Object obj)
GetHashCode Method       System.Int32 GetHashCode()
GetType     Method       System.Type GetType()
ToString    Method       System.String ToString()
Fabric      NoteProperty System.String Fabric=00:00:00:00:00:00:00:00
Flags       NoteProperty System.String Flags=0000f0a5
HBADetail   NoteProperty System.Object[] HBADetail=System.Object[]
Host        NoteProperty System.String Host=Server1
LUN         NoteProperty System.String LUN=01
MFG         NoteProperty System.String MFG=Emulex Corporation
NodeWWN     NoteProperty System.String NodeWWN=20:00:00:00:11:11:11:11
PortWWN     NoteProperty System.String PortWWN=10:00:00:00:11:11:11:11

Here is the Script

Param($List,$HostName,[switch]$FullDetail,[switch]$Verbose)
Begin{
    $erroractionpreference = "SilentlyContinue"
    $HBACMDPath = "<path To HbaCmd.exe>"
    function CreateHBAListObj{
        Param($srv)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" ListHBAs
        foreach($item in $result)
        {
            $parsd = $item.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)
            switch -regex ($parsd[0])
            {
                "^Port" {
                            $myobj = "" | Select-Object Host,PortWWN,NodeWWN,Fabric,Flags,LUN,MFG
                            $myobj.PortWWN = $parsd[1]
                            $myobj.Lun = GetTargetLun $srv $myobj.PortWWN
                        }
                "^Node" {$myobj.NodeWWN = $parsd[1]}
                "^Fabr" {$myobj.Fabric  = $parsd[1]}
                "^Flag" {$myobj.Flags   = $parsd[1]}
                "^Host" {$myobj.Host    = $parsd[1]}
                "^MFG " {
                            $myobj.MFG     = $parsd[1]
                            $objCol += $myObj
                        }
            }
        }
        $objCol
    }
    function CreateHBAInfoObj{
        Param($srv,$wwn)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" HBAAttrib $wwn

        $myobj = "" |Select-Object Host,MFG,SN,Model,ModelDesc,NodeWWN,NodeSymname,
                                   HWVersion,ROM,FW,VenderID,Ports,DriverName,DeviceID,HBAType,
                                   OpFW,SLT1FW,SLT2FW,IEEEAddress,BootBios,DriverVer,KernelVer
        foreach($item in $result)
        {
            $parsd = $item.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)
            switch -regex ($parsd[0])
            {
                "^Host"         {$myobj.Host         = $parsd[1]}
                "^Manufacturer" {$myobj.MFG          = $parsd[1]}
                "^Serial"       {$myobj.Sn           = $parsd[1]}
                "^Model  "      {$myobj.Model        = $parsd[1]}
                "^Model Desc"   {$myobj.ModelDesc    = $parsd[1]}
                "^Node WWN "    {$myobj.NodeWWN      = $parsd[1]}
                "^Node Symname" {$myobj.NodeSymname  = $parsd[1]}
                "^HW"           {$myobj.HWVersion    = $parsd[1]}
                "^Opt"          {$myobj.ROM          = $parsd[1]}
                "^FW"           {$myobj.FW           = $parsd[1]}
                "^Vender"       {$myobj.VenderID     = $parsd[1]}
                "^Number"       {$myobj.Ports        = $parsd[1]}
                "^Driver Name"  {$myobj.DriverName   = $parsd[1]}
                "^Device"       {$myobj.DeviceID     = $parsd[1]}
                "^HBA Type"     {$myobj.HBAType      = $parsd[1]}
                "^Operational"  {$myobj.OpFW         = $parsd[1]}
                "^SLI1 FW"      {$myobj.SLT1FW       = $parsd[1]}
                "^SLI2 FW"      {$myobj.SLT2FW       = $parsd[1]}
                "^IEEE"         {$myobj.IEEEAddress  = $parsd[1]}
                "^Boot "        {$myobj.BootBios     = $parsd[1]}
                "^Driver Ver"   {$myobj.DriverVer    = $parsd[1]}
                "^Kernel "      {$myobj.KernelVer    = $parsd[1]
                                 $objCol += $myObj}
            }
        }
        $objCol
    }
    function GetTargetLun{
        Param($srv,$wwn)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" TargetMapping $wwn
        [int]$lun = 0
        switch -regex ($result)
        {
            "^SCSI OS Lun" {$lun = $_.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)[1].trim()}
        }
        "{0:x}" -f $lun
    }
    function Ping-Server {
        Param([string]$srv)
        if($srv -eq ""){return $false}
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
    Write-Host
    if($verbose){$VerbosePreference = "Continue"}
}
Process{
    if($_)
    {
        Write-Host "Getting HBA Info from $_"
        if($FullDetail)
        {
            $MyObject = CreateHBAListObj $_
            $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
            $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
            $MyObject
        }
        else
        {
            $MyObject = CreateHBAListObj $_
            $MyObject
        }
    }
}
End{
    if($list)
    {
        $servers = Get-Content $list
        Write-Host "Running HBA Check against Servers in $list"
        foreach($server in $servers)
        {
            if($server -ne "")
            {
                if(ping-server $server)
                {
                    Write-Host "Getting HBA Info from $server"
                    if($FullDetail)
                    {
                        $MyObject = CreateHBAListObj $server
                        $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
                        $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
                        $MyObject
                    }
                    else
                    {
                        $MyObject = CreateHBAListObj $server
                        $MyObject
                    }
                }
                else
                {
                    Write-Host "$Server not Pingable `n" -foregroundcolor RED
                }
            }
        }
    }
    if($HostName)
    {
        Write-Host "Running HBA Check against Servers in $HostName"
        if(ping-server $HostName)
        {
            Write-Host "Getting HBA Info from $HostName"
            if($FullDetail)
            {
                $MyObject = CreateHBAListObj $HostName
                $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
                $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
                $MyObject
            }
            else
            {
                $MyObject = CreateHBAListObj $HostName
                $MyObject
            }
        }
        else
        {
            Write-Host "$HostName not Pingable `n" -foregroundcolor RED
        }
    }
    Write-Host
}

VMWare and Powershell, The Early Years… The Life Before VMWare Powershell CMDLets

I am on the CTP for the VMWare Powershell Toolkit, and although I am unable to release any Information. I thought it would be a good idea to post some Powershell VMWare commands (old school) before they go live with the REAL Deal.

Hal from TechProsaic wrote a blog post here http://halr9000.com/article/445 about Using Plink to get Information about ESX and creating a custom object for the VM’s that the ESX host currently runs. I really liked the script, but I wanted a little more out of it. So I modified it (sorry Hal) and add some functionality. Below is a list of things it does.

  • Get Virtual Machines Running on ESX Host. Returns Custom Object
  • Provides a similar function to Get-Process from the ESX Host
  • Will Run a generic Command on the ESX Host
  • NOTE: At lines 16/31/62 I am having some issues with syntax highlight changing these to email addresses. Please make user the end up as $user @ $srv (no spaces)

    You can also get a working copy here http://powershellcentral.com/scripts/54

    # Invoke-VMCommand.ps1
    # Purpose     : Run a remote command and return the results
    # Requirements: plink.exe from the Putty project must be in $env:path
    # Use -help parameter for instructions

    Param (
        $VMHost,
        $username,
        $Command,
        [switch]$Help,
        [switch]$Verbose
    )

    # Obtains list of VMX (config files) corresponding to each VM on a given ESX server
    function GetVMX ($user, $pass, $srv) {
        $cmd = "plink.exe $user@$srv -pw $pass"
        $cmd += " vmware-cmd -l"
        Write-Verbose "Command line: $cmd"
        $VMList = Invoke-Expression $cmd
        $collOut = @()
        $VmList | ForEach-Object {
            $objOut = "" | Select-Object VmHost, VmName, VMXpath, HasSnapshot # create our output object with desired properties
            $objOut.VmHost = $srv
            $objOut.VMXpath = $_
            $objOut.VmName = (Split-Path $_ -Leaf) -replace ".vmx$"
            $collOut += $objOut
        }
        $collOut
    }
    function Get-ESXProcess($user, $pass, $srv){
        $cmd =  "plink.exe -t $user@$srv -pw $pass "
        $cmd += "`"ps -Af | grep `’`’`""
        Write-Verbose "Command line: $cmd"
        $results = invoke-Expression $cmd
        $colObj = @()
        foreach($result in $results)
        {
            if($result -match "^UID"){continue}
            $myobj = "" | Select-Object UID,PID,PPID,C,STIME,TTY,TIME,CMD
            $ary = $result.split([string[]]" ",[System.StringSplitOptions]::RemoveEmptyEntries)
            $myobj.UID   = $ary[0]
            $myobj.PID   = $ary[1]
            $myobj.PPID  = $ary[2]
            $myobj.C     = $ary[3]
            $myobj.STIME = $ary[4]
            $myobj.TTY   = $ary[5]
            $myobj.Time  = $ary[6]
            $proc = $null
            write-verbose "Length: $($ary.Length)"
            for($i = 7;$i -le $ary.Length;$i++)
            {
                $proc = "$proc $($ary[$i])"
                write-Verbose "Adding [$i] $($ary[$i])"
            }
            Write-Verbose "COMMAND = $proc"
            $myobj.CMD   = $proc
            $colObj += $myobj
        }
        $colObj
    }
    function RunVMCommand ($user, $pass, $srv, $vmcmd) {
        $cmd = "plink.exe $user@$srv -pw $pass "
        $cmd += "`"$vmcmd | grep `’`’`""
        Write-Verbose "Command line: $cmd"
        invoke-Expression $cmd
    }
    function GetSecurePass ($SecurePassword) {
      $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecurePassword)
      $password = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
      [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
      $password
    }

    # Returns help text
    function ShowUsage {
      $helptext = @"

    Invoke-VMCommand
      Requirements: plink.exe from the Putty project must be in your Path

    INPUT:

      VMHost     : name or IP of ESX server(s) (REQUIRED)
      UserName   : User to ssh With (REQUIRED)
      Command    : Command to Run. This can be a GetVMX, PSList, or a Custome String (REQUIRED)
      Help       : shows usage

    "@
      Write-Host $helptext
    }

    # Main
    if ($help) { ShowUsage; exit; }
    if ($verbose) { $verbosepreference = "continue" }

    $password = (Read-Host "Enter Password" -AsSecureString)

    if($Command -eq "GetVMX"){GetVMX $username (GetSecurePass $password) $VMHost}
    elseif($command -eq "PSList"){Get-ESXProcess $username (GetSecurePass $password) $VMHost}
    else{RunVMCommand $username (GetSecurePass $password) $VMHost $Command}

    More on Select-Object

    I had a few question on what I meant by “select-object does Change the object type and therefore you lose methods and properties you may expect to be there.” So Here is an example to help clarify.

    First let me show the object type Change

    26# ($dir = Get-item C:\temp).Gettype()

    IsPublic IsSerial Name                                     BaseType
    ——– ——– —-                                     ——–
    True     True     DirectoryInfo                            System.IO.FileSystemInfo

    27# ($dir = Get-item C:\temp | select-Object Fullname,Name,Parent).Gettype()

    IsPublic IsSerial Name                                     BaseType
    ——– ——– —-                                     ——–
    True     False    PSCustomObject                           System.Object
     

    Even if you specifiy PSBASE you have new object type.

    28# ($dir = Get-item C:\temp).psbase.Gettype()

    IsPublic IsSerial Name                                     BaseType
    ——– ——– —-                                     ——–
    True     True     DirectoryInfo                            System.IO.FileSystemInfo

    29# ($dir = Get-item C:\temp | select-Object Fullname,Name,Parent).psbase.Gettype()

    IsPublic IsSerial Name                                     BaseType
    ——– ——– —-                                     ——–
    True     False    PSCustomObject                           System.Object
    &gt;/pre&gt;

    Now look at what you lose using this method.

    First you will notice you have 21 items returned from Get-Member
    (I only included Properties for simplicity, but methods work the same way.)
    &lt;pre lang="posh"&gt;
    18# get-item c:\temp | Get-Member -MemberType Properties | ft Name

    Name
    —-
    PSChildName
    PSDrive
    PSIsContainer
    PSParentPath
    PSPath
    PSProvider
    Attributes
    CreationTime
    CreationTimeUtc
    Exists
    Extension
    FullName
    LastAccessTime
    LastAccessTimeUtc
    LastWriteTime
    LastWriteTimeUtc
    Name
    Parent
    Root
    Mode
    ReparsePoint
     

    After piping to Select-Object you now only have 3

    24# Get-Item C:\temp | Select-Object Name,Fullname,Parent | Get-Member -MemberType Properties | ft name

    Name
    —-
    FullName
    Name
    Parent
     

    Now.. methods before “Conversion”

    32# Get-item C:\temp | Get-Member -MemberType Methods | ft name

    Name
    —-
    Create
    CreateObjRef
    CreateSubdirectory
    Delete
    Equals
    GetAccessControl
    GetDirectories
    GetFiles
    GetFileSystemInfos
    GetHashCode
    GetLifetimeService
    GetObjectData
    GetType
    get_Attributes
    get_CreationTime
    get_CreationTimeUtc
    get_Exists
    get_Extension
    get_FullName
    get_LastAccessTime
    get_LastAccessTimeUtc
    get_LastWriteTime
    get_LastWriteTimeUtc
    get_Name
    get_Parent
    get_Root
    InitializeLifetimeService
    MoveTo
    Refresh
    SetAccessControl
    set_Attributes
    set_CreationTime
    set_CreationTimeUtc
    set_LastAccessTime
    set_LastAccessTimeUtc
    set_LastWriteTime
    set_LastWriteTimeUtc
    ToString
     

    After

    33# Get-item C:\temp | select-object FullName,Name,Parent | Get-Member -MemberType Methods | ft name

    Name
    —-
    Equals
    GetHashCode
    GetType
    ToString
     

    As you can see… Effectively they are all gone on the new object. When I first experienced this.. I thought maybe the methods were just hidden… but try to access them

    42# $dir = Get-Item C:\temp
    43# $dir.GetDirectories()

    Mode           LastWriteTime       Length Name
    —-           ————-       —— —-
    d—-     5/22/2007  1:58 PM              Console
    d—-     5/10/2007  4:36 PM              test32

    44# $dir = Get-Item C:\temp | Select-Object FullName,Name,Parent
    45# $dir.GetDirectories()
    Method invocation failed because [System.Management.Automation.PSCustomObject] doesn‘t contain a method named ‘GetDirectories‘.
    At line:1 char:20
    + $dir.GetDirectories( <<<< )

    I want to be clear… This is not a bug. Its not even a bad idea. I find it useful, but I think it confuses a lot of people. Like most people I didnt read the help before I used it or I would have seen this excerpt from get-help Select-Object:

    [code]
    If you use Select-Object to select specified properties, it copies the values of those properties from the input objects and
    creates new objects that have the specified properties and copied values.
    [/code]

    Creating Custom Objects

    halr9000 Asked “can you explain this construct for me?”

    [code]$myobj = “”| select-Object Server,Result[/code]

    IMO this is the simplest way to create custom objects.

    Effectively what happens is you set your $myobj to a empty string and pipe to select-object.
    While this may seem odd, its actually quite useful because what select-object returns is
    a PSCustomObject with the properties that you specify. This allows you to create you object
    with defined properties that you can fill out later in one line.

    I guess the “proper” way to create a custom object is to do this.

    [code]
    $myobj = new-object System.Object
    $myobj | add-member -membertype noteproperty -name Server -value $sname
    $myobj | add-member -membertype noteproperty -name Result -value $sResult
    [/code]

    While this isnt that much more work… I think its harder to read so I go with my shortcut like

    [code]
    $myobj = “” | select-Object Server,Result
    $myobj.Server = $sname
    $myobj.Result = $sResult
    [/code]

    I will give you one warning about select-object (not related, but while I’m on this subject.) select-object does Change the object type and therefore you lose methods and properties you may expect to be there. I have seen numerous post on the news groups that have this exact problem.

    If you pipe Get-ChildItem to Get-member you get tons of methods and
    properties for both System.IO.FileInfo and System.IO.DirectoryInfo
    [code]4# get-childitem | gm[/code]

    Now pipe it to select-object and do a Get-Member
    [code]
    5# (get-childitem | select-object Fullname,length) | gm

    TypeName: System.Management.Automation.PSCustomObject

    Name MemberType Definition
    —- ———- ———-
    Equals Method System.Boolean Equals(Object obj)
    GetHashCode Method System.Int32 GetHashCode()
    GetType Method System.Type GetType()
    ToString Method System.String ToString()
    FullName NoteProperty System.String FullName=C:\Windows\System32\0409
    length NoteProperty length=null
    [/code]

    Just be careful to remember this. I actually avoid using select-object to filter objects simply because of this. Instead… I just use the properties.