Author Posts

November 16, 2014 at 7:54 am

Hi,
I have started learning PS few months back, i don't have any prior scripting experience.
I just have the knowledge of Programming basics.I started with Technet/scriptcenter recorded
webcasts, Don Jones all PS tutorials available on YouTube, MVA JumpStarts and i also follow
DJones Ebooks PS in a month of lunches and PS ToolMaking in a ....,
After months of practice i feel good with the ps cmdlets and its syntax but where scripting is
concerned i am lost, don't know the approach, the thought process, the script which i have
uploaded is totaly written by me in PS ISE, the requirement is to get bios,processor,ip,memory,
drive,process,info, i want a log to be created each time i run it.
My question is please have a look on the code and suggest is this a good approch or can the code
be more compact, and after each Cmdlet instead of writing output can't we have only one line to
log all the info?
This may not be a challenging question to you but i need proper guidance.
and a request to Don Jones , Sir please record a free scripting tutorial, and it should be a senario
based such as take a fictious company and some fictious requirements like getting the ipaddress,
dns,domain info and many other things and logging it to a text file, the how to approach, how to start
the script from the scratch etc, how the thought process should be.
It will be a great help for the PS Beginners like me and many others.
Thanks,
Sikander Mirza.

November 16, 2014 at 6:08 pm

Don (and others) have been providing exactly what you're requesting for years now. I haven't read the Month of Lunches book, but from the chapter list it looks like you would want to focus on Chapter 19, 'Input and Output.' There is a ton out there on best practices. Try here to start:

I have used Don's video instruction on CBT Nuggets and it's fantastic, but his books also cover a lot of the same material so you should be good to go with the one you already have.

One of the most important rules you need to grasp right off the bat is to output objects, not text. It appears you have heard that you shouldn't be using Write-Host since you are using Write-Output instead, but you are mostly just outputting strings which is essentially the same thing. There are tons of articles out there that detail how you can combine output from several sources into a single object to output. Here is a classic article that details the many ways you can create your own objects and even shows examples of combining output from different WMI classes:

http://technet.microsoft.com/en-us/magazine/hh750381.aspx

Some tips I would start with:

1. Do less. Separate all of the logging to a file and displaying to the console stuff out. You only need to get information and output objects in your function. You can worry about writing to a file later when you run the function.

2. You're using Write-Output on almost every line. You don't need it. Anything in your function that doesn't get captured in a variable will be output to the console.

3. Forget about all the Select-Objects your are using when creating your variables. Just capture the object you need and you can access it's properties later. Most of the time you only need Select-Object at the command line. It can be used as a shortcut to custom objects, but the syntax is god-awful.

4. Don't use square brackets in your property names. These are special characters in Powershell so accessing the properties could become confusing.

5. Figure out what you want to do before you start writing a function. This is the most important lesson I have learned. We tend to run commands to figure things out and then simply copy those commands into a script try to turn that into a function. Sometimes what you end up with is a huge mish-mash of related commands and duplicated work. I have started to outline every single function I write prior to actually writing any code. I write comments into the function that describe what it should be doing and then I find the appropriate way to achieve that behavior.

Here is a short example of what I might do with some of the information you are gathering:

Function Get-ComuterInfo {
[CmdletBinding()]
Param (
    [Parameter(ValueFromPipelineByPropertyName = $true,ValueFromPipeline = $true)]
    [string]$Computername = $env:ComputerName
)

    PROCESS {
        $Computer = Get-WmiObject -Class win32_ComputerSystem -ComputerName $ComputerName
        $Bios = Get-WmiObject -Class Win32_Bios -ComputerName $ComputerName
        $Processor = Get-WmiObject -Class Win32_Processor -ComputerName $ComputerName

        [PsCustomObject]@{
            ComputerName   = $ComputerName
            MemorySizeInGB = $Comp.TotalPhysicalMemory / 1GB
            BIOSVersion    = $Bios.SMBIOSVersion
            Processor      = $Processor.Name
        }
    }     
}

Later if you need to output this to a file you can just run it either use Out-File, or even better use Export-Csv:

Get-ComputerInfo | Export-Csv c:\ComputerInfo.csv

November 17, 2014 at 6:55 am

Thanks Matt for the reply, the tips you mentioned are valuable, i will work on that and the code example which you have posted gives me an insight on how the script process should be.

November 17, 2014 at 9:13 am

Please feel free to reply if you need further clarification on any points of the example. I'll check back in later to see if you're doing ok.

Good luck!

November 19, 2014 at 11:51 am

{Function Get-CompuInfo {
    [CmdletBinding()]

    Param (
        [Parameter(Mandatory=$true,
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName)]        
        [String[]]$ComputerName ='localhost'

    )

    Begin{}
    
    Process{
        
    foreach ($Computer in $ComputerName) {
        $Date = Get-Date
        $Memory = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer
        $Bios = Get-WmiObject -Class Win32_Bios -ComputerName $computer
        $Processor = Get-WmiObject -Class Win32_processor -ComputerName $computer
        $Service = Get-Service -Name BITS
        $OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer
        $IP = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $computer
        $Disk =  Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer
        $Process = Get-Process |sort CPU -Descending | select -First 3
        $EventLog = Get-EventLog -LogName Application -EntryType Error -Newest 2

        [PsCustomObject]@{
            'Date'                    = $Date.DateTime;
            'ComputerName'            = $env:COMPUTERNAME;
            'MemorySizeinGB'          = $Memory.TotalPhysicalMemory /1GB -as [Int];
            'BiosVersion'             = $Bios.SMBIOSBIOSVersion;
            'Processor'               = $Processor.Name;
            'ServiceName'             = $Service.Name;
            'ServiceStatus'           = $Service.Status;
            'OSVersion'               = $OS.Version;
            'IPAddress'               = $IP.IPAddress[7];
            'LoggedUser'              = $env:USERNAME;
            'Top3CpuUtilizingProcess' = $Process.name;
            'New2AppErrorLog'         = $EventLog.EventID; 
            
            }

     
                 
        
        
        }
    }
    
    End{}
     
 }}

Hi Matt,
I have uploaded above my revised code, please have a look and guide me what more i can do to
make things look ease.
2.second thing is i came across a blog post on Using Advance Functions in PS, the author used $Properties@{} instead of [PSCusumObject]@{}.
what is the difference between these two, i have tried both methods for custom output data and both
works fine and when i do Get-Member as below the result are same.
. ./CompuInfo.ps1
Get-CompuInfo -ComputerName localhost | gm
TypeName: System.Management.Automation.PSCustomObject

{$Properties=@{ 
            'Date'                    = $Date.DateTime;
            'ComputerName'            = $env:COMPUTERNAME;
            'MemorySizeinGB'          = $Memory.TotalPhysicalMemory /1GB -as [Int];
            'BiosVersion'             = $Bios.SMBIOSBIOSVersion;
            'Processor'               = $Processor.Name;
            'ServiceName'             = $Service.Name;
            'ServiceStatus'           = $Service.Status;
            'OSVersion'               = $OS.Version;
            'IPAddress'               = $IP.IPAddress[7];
            'LoggedUser'              = $env:USERNAME;
            'Top3CpuUtilizingProcess' = $Process.name;
            'New2AppErrorLog'         = $EventLog.EventID;
         
            }
         
         $obj=New-Object -TypeName psobject $Properties
         Write-Output $obj }

Now we can type as Get-CompuInfo -ComputerName localhost | Out-File e:\data.txt or a ExportCSV,
but what, say a company wants to just run this script and do-not want to dot source it or use it as a module,
just run as "./CompuInfo.ps1" at the console, and each time they run they want a log file or say results file
txt or csv automatically created by present date, they don't want to specify file path or name, it should be
handled inside the script, so what changes we have to make in the script to make it work.

Many Thanks

November 20, 2014 at 10:18 am

Looks like you're on the right track! I think you're fine with what you have here.

As far as the method used to create the output object, both are acceptable. In Powershell 1.0 New-Object was the only way to do this, but we have lots of ways to create custom objects now. I like the [PsCustomObject] type accelerator because it's fast and concise, but New-Object is perfectly fine. BTW, you don't necessarily have to capture the custom object in a variable and then output it – you can just run New-Object and it will go to Out-Host by default:

New-Object -TypeName psobject $Properties

Keep on scripting!