Tuesday, May 7, 2013

Scripting Games 2013 Advanced Event 2 - My Submission

If you have not already, I highly recommend you work on the events for the Scripting Games. I feel like I'm learning so much within a few short days mainly due to the fact that I have very specific requirements outlined by the event, as well as knowing that tons of people will potentially see my submissions. Today I'll be talking about it during the Lunch-n-Learn I host at work, so seeing constructive and accurate criticism is welcomed (username michaellwest). Here is what I have submitted, please let me know your thoughts on how I can improve the script.
  • The begin scriptblock contains a hashtable of settings to use for the function. The keys represent the Cim class name and the values represent the properties to return. You can use strings, hashtables, and scriptblocks.
  • Use CimSessionOption with the Dcom protocol to more reliably query Windows Server 2000-2008.
  • The nested foreach loops are not that great, however the keys and properties are few so the performance is still fine. Get-CimInstance is what takes a long time.
EDIT: I made an adjustment based on a post from Mike Robbins regarding the Physical memory.
function Get-ServerInventory {
    <#
        .SYNOPSIS
            Performs a hardware inventory on the specified server(s).
 
        .DESCRIPTION
            The values returned by the inventory process may be enhanced by adding to the settings hashtable in the begin scriptblock.
            The settings key is the class name. The settings values supported include string, hashtable, and scriptblock.
 
        .PARAMETER ComputerName
            Indicates the server(s) to perform a hardware inventory. The default value is localhost.
 
        .EXAMPLE
            Perform an inventory on localhost.
 
            PS C:\> Get-ServerInventory
 
            TotalPhysicalMemory : 21356912640
            ProcessorName       : Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
            Version             : 6.1.7601
            SerialNumber        : 00371-OEM-8992671-00008
            ComputerName        : WIN7DEV01
            Cores               : 4
            Sockets             : 1
 
        .EXAMPLE
            Perform an inventory on 1-N servers with an array or using Get-Content.
 
            PS C:\> "WIN2K01","WIN2K02","WIN2008R201" | Get-ServerInventory | Format-Table -AutoSize
 
            TotalPhysicalMemory ProcessorName                            Version  SerialNumber            ComputerName Cores Sockets
            ------------------- -------------                            -------  ------------            ------------ ----- -------
                     4294148096 Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz 5.2.3790 69712-640-5906017-45214 WIN2K01          1       1
                     2146861056 Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz 5.2.3790 69712-641-5611134-45717 WIN2K02          1       1
                     4294500352 Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz 6.1.7601 55041-266-0135507-84842 WIN2008R201      1       1
 
        .LINK            
            Windows Server 2003 incorrectly reports the number of physical multicore processors or hyperthreading-enabled processors. Apply the below hotfix to correct the reported issue. 
            http://support.microsoft.com/kb/932370
 
        .LINK
            Example on retrieving the CPU count. 
            http://www.sql-server-pro.com/physical-cpu-count.html
    #>
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName=$env:COMPUTERNAME
    )
 
    begin {
        $settings = @{
                "Win32_OperatingSystem" = @("Version","SerialNumber")
                "Win32_ComputerSystem" = @({param($result,$output) $output["Capacity"] = $result | Measure-Object -Property Capacity -Sum | Select-Object -ExpandProperty Sum})
                "Win32_Processor" = @(@{n="ProcessorName";e={$_.Name}}, {
                            param($result,$output)
                            $processors = @($result)
                            if ($processors[0].NumberOfCores) {
                                $output["Cores"] = $processors.Count * $processors[0].NumberOfCores
                            } else {
                                $output["Cores"] = $processors.Count
                            }
                            $output["Sockets"] = @($processors | Where-Object {$_.SocketDesignation} | Select-Object -Unique).Count
                        })
        }
    }
 
    process {
        $sessions = $ComputerName | Select-Object @{n="ComputerName";e={$_}} | 
            New-CimSession -SessionOption (New-CimSessionOption -Protocol Dcom)
 
        foreach($session in $sessions) {
            $output = @{}
            foreach($key in $settings.Keys) {
                $result = Get-CimInstance -CimSession $session -ClassName $key
                $output["ComputerName"] = $result.PSComputerName
                foreach($property in $settings[$key]) {
                    if($property -is [string]) {
                        $output[$property] = $result.$property
                    } elseif ($property -is [scriptblock]) {
                        Invoke-Command -ScriptBlock $property -ArgumentList $result, $output
                    } elseif ($property -is [hashtable]) {
                        ($result | Select-Object -Property $property).PSObject.Properties | ForEach-Object {$output[$_.Name] = $_.Value }
                    }
                }
            }
 
            [PSCustomObject]$output
 
            Remove-CimSession -CimSession $session
        }
    }
}

Friday, April 26, 2013

Embedding Csharp in PowerShell Script

I wanted to use a "using block" found in C# to dispose of objects in PowerShell such as Streams or other object types that require the calling of Dispose. After seeing different examples here is where I stopped. I made some minor tweaks.
  1. Begin by creating a class in C#. You can also save the code in a separate file such as Code.cs. See help Add-Type -Examples for additional examples. The class must also inherit from System.IDisposable, otherwise the using-block function will complain with something like 'using-block : Cannot process argument transformation on parameter 'InputObject'. Cannot convert the "Code" value of type "Code" to type "System.IDisposable".'
  2. Create your using block with the instantiation of a new object in parentheses.
  3. Finally, in the script block place your needed logic.

Add-Type @"
using System;

public class Code : IDisposable
{
    public Code()
    {
        Name = "Michael";
    }
    
    public string Name { get; set; }

    public bool IsDisposed { get; set; }
    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);      
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!IsDisposed)
        {
            IsDisposed = true;   
        }
    }
}
"@

function using-block {
    param (
        [System.IDisposable]$InputObject = $(throw "The parameter -inputObject is required."),
        [ScriptBlock]$ScriptBlock = $(throw "The parameter -scriptBlock is required.")
    )
    try { & $ScriptBlock }
    finally {
        if ($InputObject) {
            if ($InputObject.PSBase) {
                $InputObject.PSBase.Dispose()
            } else {
                $InputObject.Dispose()
            }
        }
    }
}

using-block($c = New-Object Code) {
    $name = $c.Name
    $name # Michael
    $c.IsDisposed # False
}
$name # Not in this scope so no value
$c.IsDisposed # True
This may not be the most elegant approach but it served it's purpose.

Sunday, April 7, 2013

PowerShell Module In Session

I found it a little difficult to bring in functions into a session so I thought this would help others with what helps me get the job done.
The video goes from creating a script module to importing that module into a session.

Tuesday, April 2, 2013

Run with PowerShell Context Menu

Today I was working on our automated build process at work, and found myself running a batch file in a console window that I wanted to remain open. I setup a context menu item associated with .bat files which launches with PowerShell.
Here are the steps:
# We need to create our keys under HKEY_CLASSES_ROOT, which by default has not associated PSDrive.
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

# Set the current location to the new drive.
cd HKCR:

# Set the current location to the shell key for .bat files.
cd '.\batfile\shell'

# Create a new key called "Run with PowerShell"
New-Item -Path 'Run with PowerShell'

# Set the current location to the new key.
cd '.\Run with PowerShell'

# Create a new key called "command", which will contain the reference to PowerShell.
New-Item -Path 'command'
cd '.\command'

# Create a new "(default)" string with the command to execute PowerShell. The "%1" contains the path to the .bat file. 
New-ItemProperty -Path '.' -Name '(default)' -Value 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"'

Here is an example of the output:
PS C:\> New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

Name           Used (GB)     Free (GB) Provider      Root                  CurrentLocation
----           ---------     --------- --------      ----                  ---------------
HKCR                                   Registry      HKEY_CLASSES_ROOT

PS C:\> cd HKCR:\batfile\shell
PS HKCR:\batfile\shell> New-Item -Path 'Run with PowerShell'

    Hive: HKEY_CLASSES_ROOT\batfile\shell

Name                           Property
----                           --------
Run with PowerShell

PS HKCR:\batfile\shell> cd '.\Run with PowerShell'
PS HKCR:\batfile\shell\Run with PowerShell> New-Item -Path 'command'

    Hive: HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell

Name                           Property
----                           --------
command

PS HKCR:\batfile\shell\Run with PowerShell> cd '.\command'
PS HKCR:\batfile\shell\Run with PowerShell\command> New-ItemProperty -Path '.' -Name '(default)' -Value 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"'

(default)    : C:\WINDOWS\SysWow64\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell\command
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell
PSChildName  : command
PSDrive      : HKCR
PSProvider   : Microsoft.PowerShell.Core\Registry

Wednesday, March 27, 2013

PowerShell Github Projects

I've been working on a few projects in PowerShell. Here is the source code on Github for those interestered.

PowerShell with GUI in Xaml

PowerShell Deployment

Saturday, March 23, 2013

Discover PowerShell - Episode 5

Introduction on using the splatting feature in Windows PowerShell 3.

Disable PowerShell Remoting

Here are a few quick steps to "undo" the default changes performed by Enable-PSRemoting.
PS C:\> Disable-PSRemoting -Force
WARNING: Disabling the session configurations does not undo all the changes made by the Enable-PSRemoting or Enable-PSSessionConfiguration cmdlet. You might have to manua
lly undo the changes by following these steps:
    1. Stop and disable the WinRM service.
    2. Delete the listener that accepts requests on any IP address.
    3. Disable the firewall exceptions for WS-Management communications.
    4. Restore the value of the LocalAccountTokenFilterPolicy to 0, which restricts remote access to members of the Administrators group on the computer.
PS C:\> winrm delete winrm/config/listener?address=*+transport=HTTP
PS C:\> Stop-Service winrm
PS C:\> Set-Service -Name winrm -StartupType Disabled
PS C:\> Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name LocalAccountTokenFilterPolicy -Value 0 -Type DWord