Wednesday, June 24, 2015

Powershell Oneliner Contest 2015 - Win a Hyper-V book

I have always enjoyed taking part in most of the Powershell contests out there since Powershell is kind of a recreational administration language to me. I am thinking, for example, of the fun I had taking part in the oneliner contest (which I won) organized by fellow MVP Mike F Robbins, or of the Powershell Golf organized in 2013 by Robert Robelo on Twitter.
 
THE IDEAS BEHIND THE CONTEST
 
Being quite proud of my blog, and despite the fact that I am more of a system administrator than a developer, I have unilaterally decided to organize my self-hosted, self-managed Powershell Oneliner Contest.
 

The three ideas behind this game are that:
  1. experienced competitors (and I, of course!) should come away with a lot of tips from the brightest Powershell minds that will hopefully take part in the event
  2. novice Powershell scripters should learn that persistence pays off when looking for a solution to such a contest
  3. this is a fairly good occasion to sharpen your skills for the soon-to-start Powershell Scripting Games held by the Powershell.org community
GENERAL RULES
 
  • The contest is split into three tasks. Each task consists of a simple scenario for which you have to produce a oneliner solution.
  • Submit your solutions via a comment to this blog article by 11AM (PDT) on Wednesday, July 1st, which is the deadline.
  • You MUST submit only one task solution per comment, so that I can easily see who was the first to submit the shortest solution on a per-task basis.
  • Should you find a shorter oneliner to solve a task you are allowed to post additional comments (just remember to sign your comments so that I know who's who).
  • Aliases are of course accepted
  • Backticks accepted for readability
  • No semi-colons
  • UPDATE - No Here-String
  • UPDATE - Please don't sign your comments as Unknown or I won't be able to know who you are.
  • If you use a Powershell version other than v4, I am okay with it, but please mention it in the comment so that I can test it on the same version. Powershell v5 is welcome.
  • Entries (comments) will not be made public until after the submission deadline.

SCORING
  • The shortest solution in terms of chars wins.
  • The first person to produce the shortest working solutions to a task will get 1 point, the second 2 points, the third 3 points and so on.
  • The person with the lowest total score over the three mandatory tasks will be the winner.
  • The winner will be announced on Saturday, July 4th on this blog and the prize will be awarded at that time.
  • I'll be the only judge.

THE PRIZE
 
What? Is there a prize? Yes, of course. The prize will be a printed copy of the excellent book 'Hyper-V Best Practices' (5-Stars on amazon.com) by fellow MVP Benedict Berger  (@Benedict_Berger) offered by Packt Publishing!!!
I was the reviewer of the book as well of the ton of Powershell code it contains. I can tell you this is the book to read in these Hyper-V times!


As a bonus, and if the winner agrees, he/she will intervene as a guest blogger on this blog and will explain how one goes about learning the hidden bits of Windows Powershell. Sounds cool, doesn't it?

Let's now dive into the contest tasks.
 
TASK 1 - Who's taller?
 
This is a rather easy challenge and I hope it will make for a good warm-up session for your Powershell muscles, especially if you are a beginner. Given a $n variable showed below, write the shortest possible oneliner that outputs the absolute value of the largest double-precision floating-point number in the array.
$n = -1,-2,-5,-8.9,'b',-9.11,-6,-3,-2,-9.1,-1,-1.4,'a'

Expected output
Task-specific rules
  • The object in the output can be of any type
  • UPDATE - The one-liner must work also with different values of $n

TASK 2 - Can you count to five?

Time to delve into something harder. Write the shortest possible Powershell oneliner that outputs the number 12345.

Expected output
 
Task-specific rules
  • You are not allowed to use the digits 0 to 9 in your oneliner
  • The object in the output must be of type Int32
  • Your displayed output shouldn't contain any other char

TASK 3 - Powershell is the secret word

I hope that by this time you are enjoying the competition. It's time for the last hard task. Write the shortest possible Powershell oneliner that outputs the word 'PowerShell' starting from a text string composed only of uppercase Xs' and whitespaces.

Expected output
Task-specific rules
  • The string composed of X's and whitespaces must be part of the oneliner (backticks accepted!) and not be a variable defined on a separate line
  • You can use as many X's and empty spaces you like or need
  • P and S in the output must be uppercase
  • UPDATE - No Here-String
  • UPDATE - I consider jokes answers like "X "|%{"Powershell"}
On your marks, set, go! And remember to have fun, it's just a game meant to produce something useful for the growing and growing Powershell community!

UPDATE - Check-Task Function

I have decided to update this blog post with a function that will allow you to check if your proposed solutions are correct in terms of output and respect the task assignments. Kudos for this function go to my friend Joey!

function Check-Task ([int] $Task, [scriptblock] $Command) {
        $n = -1,-2,-5,-8.9,'b',-9.11,-6,-3,-2,-9.1,-1,-1.4,'a'
        $result = & $command
        $check = $false
        switch ($Task) {
            1 { $check = $result -eq '1.4' -or 
                [double]::Parse("$result") -eq 1.4 }
            2 {
                $check = ($result -is [int] -and $result -eq 12345) -and
                    ("$Command" -notmatch '[0-9]')
              }
            3 {
                $check = ($result -is [string] -and $result -ceq 'PowerShell') -and
                    ("$Command" -match '([''"])[X`\s]+\1')
            }
        }
        if (!$check) {
            Write-Host -Fore Yellow "Expected: $((1.4,12345,'PowerShell')[$Task - 1])"
            Write-Host -Fore Red    "Actual:  $result"
        }
        $color = ('Red','Green')[$check]
        Write-Host -Fore $color ('{0,4}: {1}' -f "$Command".Trim().Length,$command)
    }
Sample usage:
Check-Task 1 { $n.gettype() }
Do not hesitate to test your solutions before posting them to my blog! Better safe than sorry.

SPREAD THE WORD

If you want to give some visibility to this contest, feel free to twit about it. You can use the hashtags #poshcontest2015 and #powershell so that other competitors can share their thoughts (not the solutions of course!).

Thanks again to Packt Publishing (and to Mary in particular) for sponsoring this event!

Tuesday, June 2, 2015

Copy-Item improvements to support file copy over WinRM to NanoServers

If you follow this blog (you should!), then you know that I have just published a post on the procedure to install a Microsoft NanoServer.

If you read through the whole post, you know that this small footprint operating system is supposed to be managed through Powershell Remoting since it has no local logon capabilities.

Now, if you look well into the new cmdlets/parameters that come with the Windows Management Framework 5 (the one that comes with Windows 2016 TP2), you'll see that Copy-Item now supports file copy operations through WinRM.

The new key parameters are -ToSession and -FromSession.

Let's see with an example how they shoud be used., even do they are pretty self-explanatory.
 
First of all open a remoting session to the NanoServer:

$session = New-PSSession -ComputerName 192.168.1.5 -Credential administrator
Then, to copy any file to the NanoServer over WinRM, just use the following syntax:

Copy-Item -ToSession $session -Path .\samplescript.ps1 -Destination c:\temp\
The operation can be performed both directions, so to retrieve a file just use:

Copy-Item -FromSession $session -Path c:\results.txt -Destination .
If you liked this post, please share!

How to build your first fantastic Nano Server

As it has been announced in recent events such as Ignite, Microsoft is working hard for the introduction of Nano Server, a cloud-oriented, GUI-less, small-everything, Powershell-managed version of Windows Server. This server version comes with no logon capability, which means that management tasks are performed remotely, from the very first logon. And, since the system is built to be extremely light, we can suppose that it will be blatantly fast for the specific workloads that come from the devops/virtualization/containerization world.
 
NanoServer goes even further than Server Core in terms of lightness
Sounds good, you say. But how to test it? Well, today Nano Server is part of Windows 2016 Technical Preview 2, which has been announced less than a month ago and that I have had the occasion to test in one of my labs. It was a tricky job that I am going to detail as long as the issues I faced during the deployment.
 
STEP 1: PREPARE THE VHD

The starting point is the NanoServer folder stored in the Windows 2016 TP2 iso:


The folder size is a mere 181 MB, and it contains a .wim file as well as the following six file cabinets:

Directory: H:\NanoServer\Packages

Mode            LastWriteTime     Length Name
----            -------------     ------ ----
--r--    09/05/2015     03:01   10849819 Microsoft-NanoServer-Compute-Package.cab
--r--    09/05/2015     03:01    7690908 Microsoft-NanoServer-FailoverCluster-Package.cab
--r--    09/05/2015     03:01     327347 Microsoft-NanoServer-Guest-Package.cab
--r--    09/05/2015     03:01   12785830 Microsoft-NanoServer-OEM-Drivers-Package.cab
--r--    09/05/2015     03:01    6900267 Microsoft-NanoServer-Storage-Package.cab
--r--    09/05/2015     03:01      49656 Microsoft-OneCore-ReverseForwarders-Package.cab
Let's begin with building the source repository by copying the whole content of this folder:

Copy-Item H:\NanoServer\* D:\nanoserver -Recurse
Then we have to convert the WIM file contained in the Windows iso file to a VHD that we can mount in Hyper-V. This is easily achieved with the Powershell script that Pronichkin developed and that you can download from here.

Put the script in the same repository you created above:

Set-Location D:\NanoServer

.\Convert-WindowsImage.ps1 -WIM .\NanoServer.wim -VHD .\NanoServer.vhd -DiskType Fixed -VHDFormat VHD -SizeBytes 1GB -Edition 1

Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 8
Copyright (C) Microsoft Corporation.  All rights reserved.
Version 6.3.9600.7.amd64fre.fbl_core1_hyp_dev(mikekol).140217-3000 Release to Web

INFO   : Image 1 selected ()...
INFO   : Creating fixed disk...
INFO   : Attaching VHD...
INFO   : Disk initialized with MBR...
INFO   : Disk partitioned...
INFO   : Volume formatted...
INFO   : Access path (D:\) has been assigned...
INFO   : Applying image to VHD. This could take a while...
INFO   : Signing disk...
INFO   : Image applied. Making image bootable...
INFO   : Fixing the Device ID in the BCD store on VHD...
INFO   : Drive is bootable. Cleaning up...
INFO   : Closing VHD...
INFO   : Closing Windows image...
INFO   : Done.
As you can see, I created a 1GB disk, and this is enough for a test environment.

STEP 2: ADD PACKAGES

Next we need to stream in some dlls and packages into the VHD.

New-Item Dism -ItemType Directory
Set-Location Dism
Copy-Item H:\Sources\api*downlevel*.dll
Copy-Item H:\Sources\*dism*
Copy-Item H:\Sources\*provider*
This will copy 30 files in your Dism folder. 
Preparing the vhd...
Now mount the image:

Mount-DiskImage D:\NanoServer\NanoServer.vhd
In my case it got mounted as G:

Then to add the packages I mentioned before (the ones under H:\NanoServer\Packages), you can leverage the Add-WindowsPackage cmdlet:

ISSUE 1: LOW LEVEL DISM AGAINST LEVEL UP IMAGE

In my case I encountered an issue with this action when I performed it on my Windows 2012 R2 server:

Add-WindowsPackage -Path G: -PackagePath *.cab
Add-WindowsPackage : To service this Windows image requires the latest version of the DISM. See http://go.microsoft.com/fwlink/?LinkId=293395 to find the latest version of DISM, 
and http://go.microsoft.com/fwlink/?LinkId=293394 to learn how to install the latest version of DISM from the ADK on your computer.
At line:1 char:1
+ Add-WindowsPackage -Path G: -PackagePath *.cab
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Add-WindowsPackage], COMException
    + FullyQualifiedErrorId : Microsoft.Dism.Commands.AddWindowsPackageCommand
In the DISM log I found the following error:

DISM Manager: PID=3092 TID=4604 It is an unsupported scenario to service an up level image using low level dism. - CDISMManager::CreateImageSessionFromLocation(hr:0x80070032)

I checked the version of the DISM cmdlets on Windows 2012 R2:

Add-WindowsPackage -Path G: -PackagePath .\Microsoft-NanoServer-Compute-Package.cab -Verbose
VERBOSE: Dism PowerShell Cmdlets Version 6.3.0.0
On Windows 10 Insider Preview we have version 10:

VERBOSE: Dism PowerShell Cmdlets Version 10.0.0.0
I tried to solve this by downloading the Windows Assessment and Deployment Kit (Windows ADK) for Windows 8.1 Update which I found here.

For your information, the adksetup.exe must be run from c:\ as an administrator otherwise it will fail with such an error:

Could not acquire privileges; GLE=0x514
Returning status 0x514

In any case it kept not working on Windows 2012 R2.

SOLUTION TO ISSUE 1: USE WINDOWS 10 DISM

The known workaround is to use DISM from either Windows 10 or Windows Server 2016 Technical Preview 2 machine. In my case I luckily had a freshly installed Windows 10 CTP2 sitting there in a VM. I just copied the vhd over there, enable the administrator account, which is needed to be able to mount the vhd file, through the command

net user administrator /active:yes

On Windows 10, Add-WindowsPackage worked flawlessly (apart for the drivers package which took much longer):
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-NanoServer-Compute-Package.cab
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-NanoServer-FailoverCluster-Package.cab
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-NanoServer-Guest-Package.cab
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-NanoServer-OEM-Drivers-Package.cab
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-NanoServer-Storage-Package.cab
Add-WindowsPackage –Path G:\ –PackagePath C:\NanoServer\Packages\Microsoft-OneCore-ReverseForwarders-Package.cab

Add-WindowsPackage in action on my Windows 10 Enterprise Insider Preview

Once all the packages are added, I dismounted the vhd image.

Dismount-DiskImage -ImagePath C:\Nanoserver\NanoServer.vhd
The vhd file is now ready.

STEP 3: CREATE A HYPER-V VIRTUAL MACHINE

One I copied the vhd back to my Windows 2012 R2 test bed I created the VM descriptors with Powershell:

New-VM -Name 'NanoServer1' -Generation 1 -MemoryStartupBytes 512MB -SwitchName LAN -Path 'N:\Hyper-V Virtual Machines\Virtual Machines\' -NoVHD
Copy-Item 'D:\NanoServer\NanoServer.vhd' -Destination 'N:\Hyper-V Virtual Machines\'
Get-VM 'NanoServer1' | Add-VMHardDiskDrive -Path 'N:\Hyper-V Virtual Machines\NanoServer.vhd'
Start-VM 'NanoServer1'
The machine booted, but the boot took almost twenty minutes. After a long wait a black screen with an underscore appeared. I didn't really expect something on the console to show, but I must admit I was unpleasantly surprised by the long boot up times.

BUG 1: LONG BOOT-UP TIME

After a long analysis and a bunch of re-deployments, I discovered that, even do the NanoServer virtual machines show the following screen, in reality they are already pingable and manageable:

Windows Nano Server is online well before the start-up is replaced by the black screen
Once Nano is up and running, the following screen appears:

The famous black screen with un underscore of a Windows NanoServer
As you can see it differs from the welcome screen of a standard Windows 2016 Technical Preview 2 installation in the fact that it lacks logon capabilities:

Windows 2016 logon screen
At this point you can connect to it remotely using PowerShell Remoting.

STEP 4: REMOTING INTO A NANOSERVER
 
From a PowerShell prompt on a management PC, run the following two commands, replacing the IP address with the one of your Nano Server (you can check this on your local DHCP server list of leases):

Set-Item WSMan:\localhost\Client\TrustedHosts -Value 192.168.5 -Concatenate
Enter-PSSession -ComputerName 192.168.1.5 -Credential Administrator
The Set-Item cmdlet is used to add Nano server to the list of trusted hosts for Remoting.

Note that the administrator password is blank by default, so when Enter-PSSession asks for a password just hit Enter.

Once you are logged in, you can check the default name that a NanoServer is given, which is "miniwinpc".

[192.168.1.5]: PS C:\Users\Administrator\Documents> hostname
minwinpc
[192.168.1.5]: PS C:\Users\Administrator\Documents> ipconfig /all

Windows IP Configuration

   Host Name . . . . . . . . . . . . : minwinpc
   Primary Dns Suffix  . . . . . . . :
   Node Type . . . . . . . . . . . . : Hybrid
   IP Routing Enabled. . . . . . . . : No
   WINS Proxy Enabled. . . . . . . . : No

Ethernet adapter Ethernet:

   Connection-specific DNS Suffix  . :
   Description . . . . . . . . . . . : Microsoft Hyper-V Network Adapter
   Physical Address. . . . . . . . . : 00-15-5D-01-64-03
   DHCP Enabled. . . . . . . . . . . : Yes
   Autoconfiguration Enabled . . . . : Yes
   Link-local IPv6 Address . . . . . : fe80::b47c:4c39:433b:5628%4(Preferred)
   IPv4 Address. . . . . . . . . . . : 192.168.1.5(Preferred)
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Lease Obtained. . . . . . . . . . : Monday, June 1, 2015 11:07:57 AM
   Lease Expires . . . . . . . . . . : Monday, June 1, 2015 11:07:57 PM
   Default Gateway . . . . . . . . . : 192.168.1.254
   DHCP Server . . . . . . . . . . . : 192.168.1.254
   DHCPv6 IAID . . . . . . . . . . . : 67114333
   DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-1C-FE-57-D3-00-15-5D-01-64-03
   DNS Servers . . . . . . . . . . . : 192.168.1.254
   NetBIOS over Tcpip. . . . . . . . : Disabled

Tunnel adapter Local Area Connection* 2:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :
   Description . . . . . . . . . . . : Microsoft Failover Cluster Virtual Adapter
   Physical Address. . . . . . . . . : 02-92-9B-4B-EB-13
   DHCP Enabled. . . . . . . . . . . : Yes
   Autoconfiguration Enabled . . . . : Yes
Here's the output of some other generic commands on a NanoServer:
[192.168.1.5]: PS C:\> ls


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         5/6/2015  10:05 AM                EFI
d-----         5/6/2015  10:32 AM                Program Files
d-----         5/6/2015  10:05 AM                Program Files (x86)
d-r---         6/1/2015  12:40 PM                Users
d-----         6/1/2015  11:22 AM                Windows
-a----         6/1/2015   6:44 AM            300 Convert-WindowsImageInfo.txt
[192.168.1.5]: PS C:\> Get-Process

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     94       6      596       1540 ...61     0.33    380 csrss
      0       0        0          4     0               0 Idle
    615      17     3160       9900 ...86     0.34    428 lsass
    243      10     2340       9140 ...81     0.34   1076 MsMpEng
    168       8     1452       4912 ...65     0.30    416 services
     44       3      280       1076 ...56     0.23    296 smss
    189       8     1396       5240 ...75     0.09    536 svchost
    223      12     1388       5484 ...75     0.06    576 svchost
    721      20    10732      20864 ...14     1.33    636 svchost
    354      14     3976      11628 ...26     0.13    692 svchost
    110       7     1136       5540 ...74     0.08    752 svchost
     93       6     1040       4552 ...71     0.00    768 svchost
    284      15     6972      11116 ...92     0.20    780 svchost
    209      14     2216       7180 ...80     0.00    884 svchost
    567      55     6152      15288 ...63     0.89    904 svchost
    263      26     3760       9264 ...86     0.23   1000 svchost
    329       0       76         80     2     4.16      4 System
     69       7      640       3476 ...63     0.05    404 wininit
    379      36    34160      54288 ...24     9.19   1544 wsmprovhost
[192.168.1.5]: PS C:\> gcim win32_operatingsystem | fl *


Status                                    : OK
Name                                      : Microsoft Windows Server Technical Preview 2
                                            Tuva|C:\Windows|\Device\Harddisk0\Partition1
FreePhysicalMemory                        : 3883052
FreeSpaceInPagingFiles                    : 233724
FreeVirtualMemory                         : 4188272
Caption                                   : Microsoft Windows Server Technical Preview 2 Tuva
Description                               :
InstallDate                               : 6/1/2015 11:07:56 AM
CreationClassName                         : Win32_OperatingSystem
CSCreationClassName                       : Win32_ComputerSystem
CSName                                    : MINWINPC
CurrentTimeZone                           : -420
Distributed                               : False
LastBootUpTime                            : 6/1/2015 11:07:46 AM
LocalDateTime                             : 6/1/2015 12:47:26 PM
MaxNumberOfProcesses                      : 4294967295
MaxProcessMemorySize                      : 137438953344
NumberOfLicensedUsers                     : 0
NumberOfProcesses                         : 20
NumberOfUsers                             : 0
OSType                                    : 18
OtherTypeDescription                      :
SizeStoredInPagingFiles                   : 233724
TotalSwapSpaceSize                        :
TotalVirtualMemorySize                    : 4427568
TotalVisibleMemorySize                    : 4193844
Version                                   : 10.0.10074
BootDevice                                : \Device\HarddiskVolume1
BuildNumber                               : 10074
BuildType                                 : Multiprocessor Free
CodeSet                                   : 1252
CountryCode                               : 1
CSDVersion                                :
DataExecutionPrevention_32BitApplications : True
DataExecutionPrevention_Available         : True
DataExecutionPrevention_Drivers           : True
DataExecutionPrevention_SupportPolicy     : 2
Debug                                     :
EncryptionLevel                           :
ForegroundApplicationBoost                :
LargeSystemCache                          :
Locale                                    : 0409
Manufacturer                              : Microsoft Corporation
MUILanguages                              : {en-US}
OperatingSystemSKU                        : 109
Organization                              : Microsoft
OSArchitecture                            : 64-bit
OSLanguage                                : 1033
OSProductSuite                            : 272
PAEEnabled                                :
PlusProductID                             :
PlusVersionNumber                         :
PortableOperatingSystem                   : False
Primary                                   : True
ProductType                               : 3
RegisteredUser                            : Microsoft
SerialNumber                              :
ServicePackMajorVersion                   : 0
ServicePackMinorVersion                   : 0
SuiteMask                                 : 272
SystemDevice                              : \Device\HarddiskVolume1
SystemDirectory                           : C:\Windows\system32
SystemDrive                               : C:
WindowsDirectory                          : C:\Windows
PSComputerName                            :
CimClass                                  : root/cimv2:Win32_OperatingSystem
CimInstanceProperties                     : {Caption, Description, InstallDate, Name...}
CimSystemProperties                       : Microsoft.Management.Infrastructure.CimSystemProperties
As a side note, notice that the good old Get-WMIObject (alias gwmi) is not implemented on NanoServer:

[192.168.1.5]: PS C:\>  Get-WmiObject win32_computersystem
The term 'Get-WmiObject' is not recognized as the name of a cmdlet, function, script file, or operable program. Checkhe the spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo          : ObjectNotFound: (Get-WmiObject:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
Concerning Powershell, version 5 of the WMF is installed, exactly like on a Windows 2016:

[192.168.1.5]: PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSRemotingProtocolVersion      2.3
CLRVersion                     4.0.30319.34011
WSManStackVersion              3.0
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.10074.0
PSVersion                      5.0.10074.0
SerializationVersion           1.1.0.1
STEP 5: COMPARE POWERSHELL MODULES OF NANOSERVE AND WINDOWS 2016 TP2
 
Now it would be interesting to compare Powershell capabilities in terms of accepted cmdlets on both Windows 2016 TP2 and Windows NanoServer.

Here's what you have on a Nano Server:

[192.168.1.5]: PS C:\> Get-Command | Select Name,Module | Group Module | Sort Count -Descending

Count Name                      Group
----- ----                      -----
  140 Storage                   {@{Name=Disable-PhysicalDiskIndication; Module=Storage}, @{Name=Disable-StorageDiagn...
   92                           {@{Name=A:; Module=}, @{Name=B:; Module=}, @{Name=C:; Module=}, @{Name=cd..; Module=...
   74 Microsoft.PowerShell.U... {@{Name=Add-Member; Module=Microsoft.PowerShell.Utility}, @{Name=Clear-Variable; Mod...
   68 NetAdapter                {@{Name=Disable-NetAdapter; Module=NetAdapter}, @{Name=Disable-NetAdapterBinding; Mo...
   43 Dism                      {@{Name=Add-ProvisionedAppxPackage; Module=Dism}, @{Name=Apply-WindowsUnattend; Modu...
   38 Microsoft.PowerShell.M... {@{Name=Add-Content; Module=Microsoft.PowerShell.Management}, @{Name=Clear-Content; ...
   35 SmbShare                  {@{Name=Block-SmbShareAccess; Module=SmbShare}, @{Name=Close-SmbOpenFile; Module=Smb...
   34 NetTCPIP                  {@{Name=Find-NetRoute; Module=NetTCPIP}, @{Name=Get-NetCompartment; Module=NetTCPIP}...
   23 NetEventPacketCapture     {@{Name=Add-NetEventNetworkAdapter; Module=NetEventPacketCapture}, @{Name=Add-NetEve...
   14 EventTracingManagement    {@{Name=Add-EtwTraceProvider; Module=EventTracingManagement}, @{Name=Get-AutologgerC...
   14 CimCmdlets                {@{Name=Export-BinaryMiLog; Module=CimCmdlets}, @{Name=Get-CimAssociatedInstance; Mo...
   13 Microsoft.WSMan.Manage... {@{Name=Connect-WSMan; Module=Microsoft.WSMan.Management}, @{Name=Disable-WSManCredS...
   11 Defender                  {@{Name=Add-MpPreference; Module=Defender}, @{Name=Get-MpComputerStatus; Module=Defe...
    9 PcsvDevice                {@{Name=Clear-PcsvDeviceLog; Module=PcsvDevice}, @{Name=Get-PcsvDevice; Module=PcsvD...
    6 StorageQoS                {@{Name=Get-StorageQoSFlow; Module=StorageQoS}, @{Name=Get-StorageQoSPolicy; Module=...
    5 Microsoft.PowerShell.S... {@{Name=Get-AuthenticodeSignature; Module=Microsoft.PowerShell.Security}, @{Name=Get...
    4 PnpDevice                 {@{Name=Disable-PnpDevice; Module=PnpDevice}, @{Name=Enable-PnpDevice; Module=PnpDev...
    3 SmbWitness                {@{Name=Move-SmbClient; Module=SmbWitness}, @{Name=Get-SmbWitnessClient; Module=SmbW...
That's just a subset of what one can find on the last version of the Windows Server OS:

[192.168.1.10]: PS C:\> Get-Command | Select Name,Module | Group Module | Sort Count -Descending

Count Name                      Group
----- ----                      -----
  140 Storage                   {@{Name=Disable-PhysicalDiskIndication; Module=Storage}, @{Name=Disable-StorageDiagn...
  105 Microsoft.PowerShell.U... {@{Name=Format-Hex; Module=Microsoft.PowerShell.Utility}, @{Name=Get-FileHash; Modul...
   98                           {@{Name=A:; Module=}, @{Name=B:; Module=}, @{Name=C:; Module=}, @{Name=cd..; Module=...
   86 Microsoft.PowerShell.M... {@{Name=Add-Computer; Module=Microsoft.PowerShell.Management}, @{Name=Add-Content; M...
   85 NetSecurity               {@{Name=Copy-NetFirewallRule; Module=NetSecurity}, @{Name=Copy-NetIPsecMainModeCrypt...
   78 RemoteDesktop             {@{Name=Add-RDServer; Module=RemoteDesktop}, @{Name=Add-RDSessionHost; Module=Remote...
   68 NetAdapter                {@{Name=Disable-NetAdapter; Module=NetAdapter}, @{Name=Disable-NetAdapterBinding; Mo...
   43 Dism                      {@{Name=Add-ProvisionedAppxPackage; Module=Dism}, @{Name=Apply-WindowsUnattend; Modu...
   42 NFS                       {@{Name=Disconnect-NfsSession; Module=NFS}, @{Name=Get-NfsClientConfiguration; Modul...
   41 MsDtc                     {@{Name=Add-DtcClusterTMMapping; Module=MsDtc}, @{Name=Get-Dtc; Module=MsDtc}, @{Nam...
   35 SmbShare                  {@{Name=Block-SmbShareAccess; Module=SmbShare}, @{Name=Close-SmbOpenFile; Module=Smb...
   34 NetTCPIP                  {@{Name=Find-NetRoute; Module=NetTCPIP}, @{Name=Get-NetCompartment; Module=NetTCPIP}...
   34 NetworkTransition         {@{Name=Add-NetIPHttpsCertBinding; Module=NetworkTransition}, @{Name=Disable-NetDnsT...
   32 BranchCache               {@{Name=Add-BCDataCacheExtension; Module=BranchCache}, @{Name=Clear-BCCache; Module=...
   27 IscsiTarget               {@{Name=Expand-IscsiVirtualDisk; Module=IscsiTarget}, @{Name=Export-IscsiTargetServe...
   23 NetEventPacketCapture     {@{Name=Add-NetEventNetworkAdapter; Module=NetEventPacketCapture}, @{Name=Add-NetEve...
   20 Pester                    {@{Name=AfterAll; Module=Pester}, @{Name=AfterEach; Module=Pester}, @{Name=Assert-Mo...
   19 ScheduledTasks            {@{Name=Disable-ScheduledTask; Module=ScheduledTasks}, @{Name=Enable-ScheduledTask; ...
   18 International             {@{Name=Get-WinAcceptLanguageFromLanguageListOptOut; Module=International}, @{Name=G...
   17 PSDesiredStateConfigur... {@{Name=Configuration; Module=PSDesiredStateConfiguration}, @{Name=Find-DscResource;...
   17 DnsClient                 {@{Name=Add-DnsClientNrptRule; Module=DnsClient}, @{Name=Clear-DnsClientCache; Modul...
   17 PKI                       {@{Name=Add-CertificateEnrollmentPolicyServer; Module=PKI}, @{Name=Export-Certificat...
   16 PSScheduledJob            {@{Name=Add-JobTrigger; Module=PSScheduledJob}, @{Name=Disable-JobTrigger; Module=PS...
   14 CimCmdlets                {@{Name=Export-BinaryMiLog; Module=CimCmdlets}, @{Name=Get-CimAssociatedInstance; Mo...
   14 UserAccessLogging         {@{Name=Disable-Ual; Module=UserAccessLogging}, @{Name=Enable-Ual; Module=UserAccess...
   14 EventTracingManagement    {@{Name=Add-EtwTraceProvider; Module=EventTracingManagement}, @{Name=Get-AutologgerC...
   13 Microsoft.PowerShell.S... {@{Name=ConvertFrom-SecureString; Module=Microsoft.PowerShell.Security}, @{Name=Conv...
   13 NetNat                    {@{Name=Add-NetNatExternalAddress; Module=NetNat}, @{Name=Add-NetNatStaticMapping; M...
   13 NetLbfo                   {@{Name=Add-NetLbfoTeamMember; Module=NetLbfo}, @{Name=Add-NetLbfoTeamNic; Module=Ne...
   13 Microsoft.WSMan.Manage... {@{Name=Connect-WSMan; Module=Microsoft.WSMan.Management}, @{Name=Disable-WSManCredS...
   13 iSCSI                     {@{Name=Connect-IscsiTarget; Module=iSCSI}, @{Name=Disconnect-IscsiTarget; Module=iS...
   12 Wdac                      {@{Name=Add-OdbcDsn; Module=Wdac}, @{Name=Disable-OdbcPerfCounter; Module=Wdac}, @{N...
   11 ServerManagerTasks        {@{Name=Get-SMCounterSample; Module=ServerManagerTasks}, @{Name=Get-SMPerformanceCol...
   11 SoftwareInventoryLogging  {@{Name=Get-SilComputer; Module=SoftwareInventoryLogging}, @{Name=Get-SilComputerIde...
   11 DirectAccessClientComp... {@{Name=Disable-DAManualEntryPointSelection; Module=DirectAccessClientComponents}, @...
   11 PowerShellGet             {@{Name=Find-Module; Module=PowerShellGet}, @{Name=Get-InstalledModule; Module=Power...
   11 Defender                  {@{Name=Add-MpPreference; Module=Defender}, @{Name=Get-MpComputerStatus; Module=Defe...
   10 PackageManagement         {@{Name=Find-Package; Module=PackageManagement}, @{Name=Get-Package; Module=PackageM...
   10 PSDiagnostics             {@{Name=Disable-PSTrace; Module=PSDiagnostics}, @{Name=Disable-PSWSManCombinedTrace;...
    9 PcsvDevice                {@{Name=Clear-PcsvDeviceLog; Module=PcsvDevice}, @{Name=Get-PcsvDevice; Module=PcsvD...
    8 BitsTransfer              {@{Name=Add-BitsFile; Module=BitsTransfer}, @{Name=Complete-BitsTransfer; Module=Bit...
    7 NetSwitchTeam             {@{Name=Add-NetSwitchTeamMember; Module=NetSwitchTeam}, @{Name=Get-NetSwitchTeam; Mo...
    7 ServerManager             {@{Name=Add-WindowsFeature; Module=ServerManager}, @{Name=Remove-WindowsFeature; Mod...
    7 TLS                       {@{Name=Disable-TlsCipherSuite; Module=TLS}, @{Name=Disable-TlsSessionTicketKey; Mod...
    6 Kds                       {@{Name=Add-KdsRootKey; Module=Kds}, @{Name=Clear-KdsCache; Module=Kds}, @{Name=Get-...
    5 AppLocker                 {@{Name=Get-AppLockerFileInformation; Module=AppLocker}, @{Name=Get-AppLockerPolicy;...
    5 PSReadline                {@{Name=Get-PSReadlineKeyHandler; Module=PSReadline}, @{Name=Get-PSReadlineOption; M...
    5 Microsoft.PowerShell.D... {@{Name=Export-Counter; Module=Microsoft.PowerShell.Diagnostics}, @{Name=Get-Counter...
    4 NetworkConnectivityStatus {@{Name=Get-DAConnectionStatus; Module=NetworkConnectivityStatus}, @{Name=Get-NCSIPo...
    4 PnpDevice                 {@{Name=Disable-PnpDevice; Module=PnpDevice}, @{Name=Enable-PnpDevice; Module=PnpDev...
    4 NetQos                    {@{Name=Get-NetQosPolicy; Module=NetQos}, @{Name=New-NetQosPolicy; Module=NetQos}, @...
    4 BestPractices             {@{Name=Get-BpaModel; Module=BestPractices}, @{Name=Get-BpaResult; Module=BestPracti...
    3 SmbWitness                {@{Name=Move-SmbClient; Module=SmbWitness}, @{Name=Get-SmbWitnessClient; Module=SmbW...
    3 WindowsErrorReporting     {@{Name=Disable-WindowsErrorReporting; Module=WindowsErrorReporting}, @{Name=Enable-...
    2 ServerCore                {@{Name=Get-DisplayResolution; Module=ServerCore}, @{Name=Set-DisplayResolution; Mod...
    2 Microsoft.PowerShell.A... {@{Name=Compress-Archive; Module=Microsoft.PowerShell.Archive}, @{Name=Expand-Archiv...
    2 NetConnection             {@{Name=Get-NetConnectionProfile; Module=NetConnection}, @{Name=Set-NetConnectionPro...
    2 PSWorkflow                {@{Name=New-PSWorkflowSession; Module=PSWorkflow}, @{Name=New-PSWorkflowExecutionOpt...
    2 Microsoft.PowerShell.Host {@{Name=Start-Transcript; Module=Microsoft.PowerShell.Host}, @{Name=Stop-Transcript;...
    1 PSWorkflowUtility         {@{Name=Invoke-AsWorkflow; Module=PSWorkflowUtility}}
    1 Microsoft.PowerShell.O... {@{Name=Export-ODataEndpointProxy; Module=Microsoft.PowerShell.ODataUtils}}
As you can see NanoServer boasts the whole bunch of Storage, SmbShare or NetTCPIP cmdlets, while it lacks modules like Netlbfo, Pester, NFS or PSDesiredStateConfiguration.

CONCLUSION

As a matter of fact, NanoServer is not aimed at being an all-round do-everything operating system, like any previous Windows till now. Nano Server has been designed for the automation of Cloud services. As Snover, the inventor of Powershell and lead architect for the Windows Server Division, stated recently: "I want automation. One of the key principles of the cloud is we want configuration as code."

Today it is up to you to find the most convenient use for this newborn technology. And even if there is for sure a lot of work to be done to correct the bugs that will come up, you can already forget about old style server management. Things are changing faster and faster. Powershell somewhat opened the way to everything you see now, and NanoServer is maybe the reason why all of this happened.
 
Stay tuned for more on NanoServer and Powershell.

Tuesday, May 26, 2015

How to check for Second Level Address Translation with Powershell

Not so long ago Microsoft added Client Hyper-V virtualization technology to its last OS, Windows 8. This move has given ITPros and developers like me the possibility to test the Hyper-V technology on their laptops and build test infrastructures aimed at learning, making demos and developing management scripts.

Now, most of the requirements to run the Hyper-V Client on your Windows 8.1 laptop are pretty simple to check. You need to own a 64-bit Pro or Enterprise version of Microsoft Windows, 4 GB of RAM and a processor (either AMD or Intel) with 64-bit wide registers. All of these requirement are easy to check.

There is one more requirement which is harder to check and to understand: your processor must, I repeat, MUST be able to perform Second Level Address Translation (SLAT).

What is that?

POPEK AND GOLDBERG AND THE DIFFICULTY OF VIRTUALISING X86

It's a long story that starts way back in 1964, when virtualization was born with the arrival of the first IBM/360 mainframe and a lot of efforts were put into optimizing environments upon which enterprises could rely . In 1974, when virtualization was already a pretty widely applied technique in the world of mainframe computing, computer scientists  Gerald J. Popek and Robert P. Goldberg published the three principles of computer virtualization we all know today:
  • fidelity/equivalence
  • safety/resource control
  • performance/efficiency
They basically mean that the executed virtual machine must be able to run as fast as a physical machine, act in the same way of a physical machine and never get out of the control of the virtual machine monitor (VMM).

Requirements for a virtual machine
(extract from 'Formal requirements for virtualizable third generation architectures'
by Popek and Goldberg
1974)
These three requirements were extremely hard to respect, largely because of the inner nature of x86 processors, which copes badly (read: doesn't cope at all) with the trap-and-emulate schema.

FROM BINARY TRANSLATION TO SLAT

Through Binary Translation, Shadow Page Tables and I/O Device Emulation, scientists achieved x86 virtualization in the late 1990s, and the company named VMWare was founded in 1998.

Nonetheless, performance stayed a issue for many years, and many administrators did not want server virtualization for some specific workloads and especially for SQL. That's why virtualizing SQL Server was once unheard of, and it's still refused by old style DBAs.

Here's where SLAT comes in. And it will make your DBAs change their mind.

SECOND LEVEL ADDRESS TRANSLATION
 
SLAT is a mechanism that enables the CPU to keep the mapping between the VM virtual memory (known as Guest Virtual Address, or GVA), the VM physical memory (aka GPA, or Guest Physical Address) and the physical memory in the virtualization host (aka System Physical Address, or SPA).
 
So, since the translation is done in the processor, the hypervisor processing overhead is significantly reduced.

There are many virtualization gurus out there explaining the ins and outs of SLAT as well as the advantages of this technology over older memory translation mechanisms. The important concept to grasp is that the goal of the Secondary Level Address Translation is to increase performance of your virtual machines by adding a new improved memory management capability inside the processors.

The third requirement of Popek and Goldberg is fulfilled. Virtual machines have finally become efficient and Microsoft is forcing us to take advantage of the most recent technology achievements by making SLAT mandatory.

HOW TO CHECK FOR SLAT
 
Now, which is the way to check for SLAT support on your computer? Powershell is one of the answers. There are others of course, like the small but powerful utility named Coreinfo (by Mark Russinovich), which if used in conjunction with the -v parameter dumps virtualization-related features including support for second level address translation.

Coreinfo must be launched with admin rights on Intel platforms otherwise you'll get the following message:

.\Coreinfo.exe -v

Coreinfo v3.31 - Dump information on system CPU and memory topology
Copyright (C) 2008-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

Intel(R) Core(TM)2 Duo CPU     T7500  @ 2.20GHz
Intel64 Family 6 Model 15 Stepping 11, GenuineIntel
Microcode signature: 000000BA

Administrator rights are required to query Intel virtualization support.
So, right-click your Powershell icon, Run as administrator and get the desired information:

.\Coreinfo.exe -v

Coreinfo v3.31 - Dump information on system CPU and memory topology
Copyright (C) 2008-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

Intel(R) Core(TM)2 Duo CPU     T7500  @ 2.20GHz
Intel64 Family 6 Model 15 Stepping 11, GenuineIntel
Microcode signature: 000000BA
HYPERVISOR      -       Hypervisor is present
VMX             *       Supports Intel hardware-assisted virtualization
EPT             -       Supports Intel extended page tables (SLAT)
In my case the laptop I am using has support for VMX, as indicated by the '*' in the output, but doesn't support EPT (which is the Intel acronym for SLAT), as indicated by the '-' in the output.
 
If I run the same tiny utility on a more recent processor I get a positive answer:

.\Coreinfo.exe -v

Coreinfo v3.31 - Dump information on system CPU and memory topology
Copyright (C) 2008-2014 Mark Russinovich
Sysinternals - www.sysinternals.com

Intel(R) Celeron(R) CPU G530 @ 2.40GHz
Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
Microcode signature: 00000028
HYPERVISOR      -       Hypervisor is present
VMX             *       Supports Intel hardware-assisted virtualization
EPT             *       Supports Intel extended page tables (SLAT)

ENTER POWERSHELL

But why lose time installing legacy exes when you have Powershell? With Powershell you can easily check for this SLAT information:

(Get-CimInstance Win32_Processor) | Format-List Name,SecondLevelAddressTranslationExtensions


Name                                    : Intel(R) Celeron(R) CPU G530 @ 2.40GHz
SecondLevelAddressTranslationExtensions : True
Just remember that the SecondLevelAddressTranslationExtensions property is supported starting from Windows 8/2012, so the cmdlet won't work on older versions of Windows.

Other virtualization-related properties you should check when thinking of implementing Hyper-V on your laptop are:
  • VirtualizationFirmwareEnabled
  • VMMonitorModeExtensions
  • DataExecutionPrevention
MAKING A TOOL

The whole requirement check could be consolidated in a simple re-usable function:

function Check-HyperV{

    <#

    .SYNOPSIS

    Checks Hyper-V requirements

    .DESCRIPTION

    This check processor compatibility with Hyper-V

    .PARAMETER ComputerName

    A computer name.

    .EXAMPLE

    Check-HyperV -ComputerName server01

    #> 

    [CmdletBinding()]

    param(

        [Parameter(Mandatory=$True,ValueFromPipeline=$True)][string[]]$ComputerName,

        [switch]$detailed

        )

        foreach($computer in $computername){

            try{

                $ComputerInfo = Get-CimInstance Win32_Computersystem -ComputerName $computer

                $ProcInfo = Get-CimInstance Win32_Processor -ComputerName $computer

                $OsInfo = Get-CimInstance Win32_Operatingsystem -ComputerName $computer

                $VirtualizationInfo = [ordered]@{

               OSArchitecture = $OsInfo.OSArchitecture

               TotalPhysicalMemory=[math]::Round($ComputerInfo.TotalPhysicalMemory/1gb)

               SLAT = $ProcInfo.SecondLevelAddressTranslationExtensions;

               VirtualizationFirmwareEnabled = $ProcInfo.VirtualizationFirmwareEnabled;

               VMMonitorModeExtensions = $ProcInfo.VMMonitorModeExtensions;

               DEP= $OsInfo.DataExecutionPrevention_available

               }

                }

            catch{

                Write-Warning "$computer failed!"

                $ErrorHappened = $True

                }

            if(!$ErrorHappened){

                if($detailed)

                    {

                    $VirtualizationInfo

                    }

                " "

                if(

                    ($VirtualizationInfo.OSArchitecture -eq '64-bit') -and

                    ($VirtualizationInfo.TotalPhysicalMemory -ge 4) -and

                    ($VirtualizationInfo.SLAT) -and

                    ($VirtualizationInfo.VirtualizationFirmwareEnabled) -and

                    ($VirtualizationInfo.VMMonitorModeExtensions) -and

                    ($VirtualizationInfo.DEP)

                  )

                    {"Hyper-V requirements filled on  $Computer!"}

                else

                    {"Time to replace $Computer..."}

                ""

                }

     }

}

Check-HyperV srv01,srv02 -detailed
I hope you have enjoyed this blog post and learned some Powershell as well as some important pieces of the history of IT, which is so easily forgotten. Do not hesitate to comment on the subject as well to correct me if I am wrong or unclear.

Be kind, share!

Monday, May 25, 2015

Moving from ping, netstat and ipconfig to Powershell

Ping, Netstat and Ipconfig are the three major legacy commands that every Windows system administrator out there has been using for decades and that, I bet, most of you are still using pretty often notwithstanding the large spread of Powershell. Well, I am not telling you to switch to Powershell today for performing basic tasks like pinging hosts or clearing your DNS cache. I am just going to show you the cmdlets that in Powershell do these tasks. And, knowing the fact that Powershell is an object-based language with a strong pipeline support, you imagine that using the Powershell way is a better option and gives you a flexible syntax as wel as re-usable results.

PING

Let's start from the first legacy command. While working in a research laboratory, back in 1983, in just one evening, Mike Muuss wrote the ancestor of the small utility that everyone of us knows as being the first tool to use to check network connectivity: ping.exe.

Microsoft came up a great amount of years later with a new cmdlet that leverages ICMP for its second version of Powershell: Test-Connection.

Test-Connection is based on the Win32_PingStatus class and since Powershell 3.0 supports a -Source parameter, which makes the use of this cmdlet particularly interesting over the use of old style pings. In fact, the -Source parameter allows you to specify the names of the computer where the ping originates from:

Test-Connection -Source Src01, Src02, Src03 -ComputerName Dst01
Also, Test-Connection allows you to ping multiple hosts at once:

Test-Connection -ComputerName Dst01, Dst02, Dst03
Using Test-Connection instead of ping allows you to take advantage of the $? variable to perform conditional actions:

if(Test-Connection nonexistingserver){'succeded'}else{'Failed'}
Test-Connection : Testing connection to computer 'nonexistingserver' failed: No such host is known
At line:1 char:4
+ if(Test-Connection nonexistingserver){'succeded'}else{'Failed'}
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ResourceUnavailable: (nonexistingserver:String) [Test-Connection], PingException
+ FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand
if(Test-Connection -ComputerName Server01 -Quiet){New-PSSession Server01}
This variable is not available for legacy commands (as I explained in this previous post), so the value of $? stays $True when you use legacy ping, and this is a limiting factor in using ping.exe in conditional statements:

if(ping nonexistingserver){'succeded'}else{'Failed'}
succeded
So now, if you are decided to replace the ping command with Test-Connection, you can simply create an alias named 'Ping' and you're set:

Set-Alias ping Test-Connection
or if you want a quicker way, just name the alias 'p':

Set-Alias p Test-Connection
IPCONFIG

The second command I want to talk of is ipconfig, which is the command used primarly to output your computer network configuration. This command was introduced 20 years ago, in 1995-1996, to Windows 95, at a time when putting the IP stack on everyone's PC was the main task of administrators and this involved typing arcane IPv4 numbers into the now forgotten winipcfg, which was in turn created for Windows 3.1 years before (remember the Wolverine stack?).

Ipconfig has four major switches as old as dinosaurs: /all, /flushdns, /displaydns and /registerdns. All of them have corresponding Powershell cmdlets, which I must admit makes them hard to remember. In any case working with network adapters has taken a great swing starting with Windows 8 and Windows 2012, when a new wave of network-oriented cmdlets belonging to the NetTCPIP module has appeared along with the underlying CIM namespaces and classes.

Ipconfig /all can be replaced by Get-NetIPConfiguration. Tough their output is more or less the same, a few consideration can be made about the improvement the Powershell cmdlet has brought to its predecessor. First of all, Get-NetIPConfiguration has switches for querying remote hosts: -Computername and -Cimsession.

You could for instance create a CIM session to pass to Get-NetIPConfiguration, even do for the moment the -CimSession switch of this cmdlet does not accept an array of computers:

$cimsession = New-CIMsession -Computername srv01

Get-NetIPConfiguration -CimSession $cimsession
Now, since Get-NetIPConfiguration returns an object, you could retrieve just one or more properties:

$ipconf = Get-NetIPConfiguration
$ipconf.DNSServer

InterfaceAlias               Interface Address ServerAddresses
                             Index     Family
--------------               --------- ------- ---------------
Wi-Fi                                4 IPv6    {}
Wi-Fi                                4 IPv4    {192.168.1.1, 192.168.1.1}
Bluetooth Network Connection         8 IPv6    {fec0:0:0:ffff::1, fec0:0:0:ffff:...
Bluetooth Network Connection         8 IPv4    {}
Ethernet                             3 IPv6    {}
Ethernet                             3 IPv4    {192.168.1.254}
Powerful, isn't it?

Now, for the lazy admin, know that the NetTCPIP module exports an alias for Get-NetIPConfiguration: gip. And since gip is way shorter than ipconfig, you have no reasons left to stick with the old command.

Ipconfig /flushdns can be replaced by Clear-DnsClientCache. This cmdlet purges the cache the client-side DNS cache that Windows stores on your computers.

The equivalent of Ipconfig /RegisterDns is Register-DnsClient. As its predecessor, Register-DnsClient invokes un update of the DNS names on all the interfaces of your computer.

NETSTAT

The last command I'd like to talk about today is the old glorious netstat. I am a huge fan of this command since it saved me in many cases when working on network connectivity issues with firewalls and application ports. Nonetheless the time has arrived to explore the Powershell way of implementing the same functions.

Netstat -a, which is used to display connections and listening ports, has evolved toward Get-NetTCPConnection since Windows 2012 and Windows 8. The output is an object with six default fields:

Get-NetTCPConnection

LocalAddress                        LocalPort RemoteAddress                       RemotePort State       AppliedSetting
------------                        --------- -------------                       ---------- -----       --------------
::                                  49157     ::                                  0          Listen
::                                  49156     ::                                  0          Listen
::                                  49155     ::                                  0          Listen
::                                  49154     ::                                  0          Listen
::                                  49153     ::                                  0          Listen
::                                  49152     ::                                  0          Listen
::                                  445       ::                                  0          Listen
::                                  135       ::                                  0          Listen
192.168.1.32                        49465     204.79.197.213                      443        Established Internet
0.0.0.0                             49157     0.0.0.0                             0          Listen
0.0.0.0                             49156     0.0.0.0                             0          Listen
0.0.0.0                             49155     0.0.0.0                             0          Listen
0.0.0.0                             49154     0.0.0.0                             0          Listen
0.0.0.0                             49153     0.0.0.0                             0          Listen
0.0.0.0                             49152     0.0.0.0                             0          Listen
0.0.0.0                             135       0.0.0.0                             0          Listen
The greatest strength of Get-NetTCPConnection over netstat is the possibility to filter on connection state on the fly:

Get-NetTCPConnection -State Established

LocalAddress     LocalPort RemoteAddress        RemotePort State       AppliedSetting
------------     --------- -------------        ---------- -----       --------------
192.168.1.32     49465     204.79.197.213       443        Established Internet
This means that you can easily filter down all connections which are in the state SYN_SENT, that is connections that are being blocked by a security firewall:

Get-NetTCPConnection -State SynSent
Get-NetTCPConnection : No MSFT_NetTCPConnection objects found with property 'State' equal to 'SynSent'.  Verify the
value of the property and retry.
At line:1 char:1
+ Get-NetTCPConnection -State SynSent
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (SynSent:State) [Get-NetTCPConnection], CimJobException
    + FullyQualifiedErrorId : CmdletizationQuery_NotFound_State,Get-NetTCPConnection
In the previous example, no connection is being blocked, so the big red error shown must be interpreted as a good news. Irony of IT.

For completeness, the possible connection states accepted by Get-NetTCPConnection are: Closed, Listen, SynSent, SynReceived, Established, FinWait1, FinWait2, CloseWait, Closing, LastAck, TimeWait and DeleteTCB.

There is another truly important switch for netstat that we are going to discuss to complete this blog post: -r. Using netstat with -r prints the routing table of your computer. The output you get is historically the same that you can get by using 'route print':

route print
===========================================================================
Interface List
  8...e4 d5 3d fd 15 99 ......Bluetooth Device (Personal Area Network)
  4...64 27 37 32 22 d1 ......Broadcom 4313GN 802.11b/g/n 1x1 Wi-Fi Adapter
  3...ec 9a 74 56 e8 89 ......Realtek PCIe GBE Family Controller
  1...........................Software Loopback Interface 1
  6...00 00 00 00 00 00 00 e0 Teredo Tunneling Pseudo-Interface
===========================================================================

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    306
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    306
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    306
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    306
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    306
===========================================================================
Persistent Routes:
  None

IPv6 Route Table
===========================================================================
Active Routes:
 If Metric Network Destination      Gateway
  1    306 ::1/128                  On-link
  1    306 ff00::/8                 On-link
===========================================================================
Persistent Routes:
  None
In Powershell the cmdlet to use to get this piece of information is Get-NetRoute. In the case of Get-NetRoute, the interesting feature to highlight is of course the possibility to sort routes by metric:

Get-NetRoute | Sort-Object RouteMetric

ifIndex DestinationPrefix             NextHop     RouteMetric  PolicyStore
------- -----------------             -------     -----------  -----------
4       0.0.0.0/0                     192.168.1.1 0     ActiveStore
8       fe80::c5ee:f21f:1980:6b41/128 ::          256   ActiveStore
4       fe80::a820:ec8e:e0c5:b536/128 ::          256   ActiveStore
3       fe80::44ad:3bde:8351:1b7/128  ::          256   ActiveStore
1       ff00::/8                      ::          256   ActiveStore
4       ff00::/8                      ::          256   ActiveStore
8       ff00::/8                      ::          256   ActiveStore
3       ff00::/8                      ::          256   ActiveStore
6       2001::/32                     ::          256   ActiveStore
3       fe80::/64                     ::          256   ActiveStore
6       ::/0                          ::          256   ActiveStore
1       ::1/128                       ::          256   ActiveStore
6       fe80::/64                     ::          256   ActiveStore
6       fe80::ffff:ffff:fffe/128      ::          256   ActiveStore
8       fe80::/64                     ::          256   ActiveStore
4       fe80::/64                     ::          256   ActiveStore
4       224.0.0.0/4                   0.0.0.0     256   ActiveStore
8       224.0.0.0/4                   0.0.0.0     256   ActiveStore
3       224.0.0.0/4                   0.0.0.0     256   ActiveStore
1       255.255.255.255/32            0.0.0.0     256   ActiveStore
4       255.255.255.255/32            0.0.0.0     256   ActiveStore
8       255.255.255.255/32            0.0.0.0     256   ActiveStore
3       255.255.255.255/32            0.0.0.0     256   ActiveStore
1       224.0.0.0/4                   0.0.0.0     256   ActiveStore
1       127.0.0.1/32                  0.0.0.0     256   ActiveStore
1       127.0.0.0/8                   0.0.0.0     256   ActiveStore
6       ff00::/8                      ::          256   ActiveStore
1       127.255.255.255/32            0.0.0.0     256   ActiveStore
4       192.168.1.255/32              0.0.0.0     256   ActiveStore
4       192.168.1.32/32               0.0.0.0     256   ActiveStore
4       192.168.1.0/24                0.0.0.0     256    ActiveStore
I hope you learned something. It's time to reimagine ipconfig, ping and netstat, guys. Time to move to Powershell and become a faster, more powerful admin. A zillion of cmdlets (network-oriented or not) are waiting for you.

The pipeline is yours.

Friday, May 22, 2015

Powershell and exit status for cmdlets and commands

In Unix and Linux systems you can use the $? variable to find out the exit status of a command. To make a long story short, when you type commands in a UNIX shell, $? always expands to the status of the most recently executed foreground command or pipeline.

Now, since the original Powershell is based on the POSIX standard (back in the eighties, guys!), which tries to standardize commands from the UNIX Korn Shell, it's somewhat natural that Powershell borrowed this $? variable from its predecessor.

Let me give a few basic tips on the use of it.

The $? is a boolean variable documented in about_Automatic_Variables, and can by design hold only two values: $True or $False.

Get-ChildItem C:\Windows\System32\drivers\etc

    Directory: C:\Windows\System32\drivers\etc

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        10/06/2009     23:00        824 hosts
-a---        10/06/2009     23:00       3683 lmhosts.sam
-a---        10/06/2009     23:00        407 networks
-a---        10/06/2009     23:00       1358 protocol
-a---        10/06/2009     23:00      17463 services

$?
True
Test-Connection nohost -Count 1
Test-Connection : Testing connection to computer 'nohost' failed: The requested name is valid, but no data of the
requested type was found
At line:1 char:1
+ Test-Connection nohost -Count 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (nohost:String) [Test-Connection], PingException
    + FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand

$?
False
Here's how to check the variable type:

$?.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Boolean                                  System.ValueType
So, while in UNIX $? returned 0 or 1, in Powershell it returns $True or $False.

There is another important variable named $LastExitCode which returns the exit command of the last Windows command run:

cmd /C exit 0

$LASTEXITCODE
0
Remember though, $LastExitCode doesn't work with Powershell commands. For instance if we check the value of the variable after a failed Powershell cmdlet, its value stays 0, which means succesful:

Test-Connection nohost -Count 1

Test-Connection : Testing connection to computer 'nohost' failed: The requested name is valid, but no data of the
requested type was found
At line:1 char:1
+ Test-Connection nohost -Count 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (nohost:String) [Test-Connection], PingException
    + FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand

$LASTEXITCODE
0
On the other side, when we check it after a failing legacy or external command, it becomes effective:
ping nohost

Ping request could not find host nohost. Please check the name and try again.

$LASTEXITCODE
1
Also, $LastExitCode isn't populated until you execute a Powershell task that returns an exit code. If you open a fresh Powershell console and search for $LastExitCode... you won't find it:

$LASTEXITCODE -eq $null

True
Nor you will find if you run a Powershell cmdlet:

gci c:\nonexistingfolder

gci : Cannot find path 'C:\nonexistingfolde
At line:1 char:1
+ gci c:\nonexistingfolder
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFoun
    + FullyQualifiedErrorId : PathNotFound,

$LASTEXITCODE -eq $null
True
The variable gets populated only when you run an external executable that returns an exit code, like the ping command:

ping nohost

Ping request could not find host nohost. Please check the name and try again.

$LASTEXITCODE -eq $null
False
I hope this brief description of the the two $? and $LASTEXITCODE variable was clear enough. If you have any specific question do not hesitate to leave it in a comment.

If you liked ths quick post, be kind, share. Alot more to come.

Thursday, April 30, 2015

How to pin Powershell to the taskbar with... Powershell

Last month I have been involved in a large Windows deployment project. One of the requirements was to be able to deliver Windows 7 computers with the Windows Powershell icon pinned to the taskbar.
 
That was not a difficult problem to solve, and, even if Windows 7 is now old stuff (especially after Windows 8.1 came out both on computers and Windows Phones) this project was nevertheless a good occasion to learn some Powershell basics that I feel like sharing with you here today.
 
The first thing to understand is that the Windows Taskbar is a user-oriented feature that Microsoft doesn't want system administrators or program installers to modify in place of the end-user. In other words, since most users don't want programs putting junk in their taskbar, Windows doesn't support you doing as such. That's the historical reason why I suppose there is no existing API to my knowledge to modify the Windows Taskbar.

Happily enough, the Shell.Application COM object comes to the rescue. Ok, I agree that's a bit old method of interacting with the Desktop by mimicking mouse-clicking, and some of you will point out that this is so VBScript. This is nonetheless the only possible way to achieve what we have been tasked for. And, for those that are curious, it's good to know that all the code I'll show below works against Windows 8 systems. What else?

Let's begin. The Shell.Application COM object is a powerful object that allow interaction with the Windows Shell. It provides a set of automation objects that can be used to access many of the Shell's features and dialog boxes.

For our task, we are going to use PowerShell to create this Shell.Application object and through it we will manipulate the Explorer programmatically.

All COM objects are created through the command: New-Object -ComObject. There really are a lot of options and possibilities for New-Object -ComObject. You could for instance open Outlook (with Outlook.Application), or Word (with Word.Application), or Excel (with Excel.Application). For our purpose we specifically need a Shell.Application type of object. Let's see how that's done.

$Shell = New-Object -ComObject Shell.Application
Once you have established this kind of connection with the Windows Shell, you can perfom most of the interaction with Windows Explorer as you'd do with your keyboard and mouse.

You could for instance open the Windows Explorer and browse the C:\ folder:

$Shell.open("C:\")
Or you could open the Windows Help:

$Shell.Help()


As you should know, Open() and Help() are methods of the Shell object. There are other useful methods, which can be investigated with Get-Member:

$Shell | Get-Member

   TypeName: System.__ComObject#{866738b9-6cf2-4de8-8767-f794ebe74f4e}

Name                 MemberType Definition
----                 ---------- ----------
AddToRecent          Method     void AddToRecent (Variant, string)
BrowseForFolder      Method     Folder BrowseForFolder (int, string, int, Variant)
CanStartStopService  Method     Variant CanStartStopService (string)
CascadeWindows       Method     void CascadeWindows ()
ControlPanelItem     Method     void ControlPanelItem (string)
EjectPC              Method     void EjectPC ()
Explore              Method     void Explore (Variant)
ExplorerPolicy       Method     Variant ExplorerPolicy (string)
FileRun              Method     void FileRun ()
FindComputer         Method     void FindComputer ()
FindFiles            Method     void FindFiles ()
FindPrinter          Method     void FindPrinter (string, string, string)
GetSetting           Method     bool GetSetting (int)
GetSystemInformation Method     Variant GetSystemInformation (string)
Help                 Method     void Help ()
IsRestricted         Method     int IsRestricted (string, string)
IsServiceRunning     Method     Variant IsServiceRunning (string)
MinimizeAll          Method     void MinimizeAll ()
NameSpace            Method     Folder NameSpace (Variant)
Open                 Method     void Open (Variant)
RefreshMenu          Method     void RefreshMenu ()
ServiceStart         Method     Variant ServiceStart (string, Variant)
ServiceStop          Method     Variant ServiceStop (string, Variant)
SetTime              Method     void SetTime ()
ShellExecute         Method     void ShellExecute (string, Variant, Variant, Variant, Variant)
ShowBrowserBar       Method     Variant ShowBrowserBar (string, Variant)
ShutdownWindows      Method     void ShutdownWindows ()
Suspend              Method     void Suspend ()
TileHorizontally     Method     void TileHorizontally ()
TileVertically       Method     void TileVertically ()
ToggleDesktop        Method     void ToggleDesktop ()
TrayProperties       Method     void TrayProperties ()
UndoMinimizeALL      Method     void UndoMinimizeALL ()
Windows              Method     IDispatch Windows ()
WindowsSecurity      Method     void WindowsSecurity ()
WindowSwitcher       Method     void WindowSwitcher ()
Application          Property   IDispatch Application () {get}
Parent               Property   IDispatch Parent () {get}
Between them you can see methods like ToggleDesktop(), which corresponds to pressing Win+D, or MinimizeAll(), which corresponds to Win+M. In our case we want first use the NameSpace method to create and return a Folder object for the folder where Windows Powershell resides: C:\Windows\System32\WindowsPowershell\v1.0\
 
The NameSpace method accepts as a parameter the path of the folder you work on as well as one of the ShellSpecialFolderConstants values. Here some examples:

(new-object -comobject shell.application).NameSpace(0x21).Self.Path
C:\Users\administrator\AppData\Roaming\Microsoft\Windows\Cookies
or

(new-object -comobject shell.application).NameSpace(0x25).Self.Path
C:\Windows\System32
In the previous example 0x25 is an hex code referring to the Windows System folder, usually c:\Windows\System32.

Unfortunately for the moment there is no special folder hex code for the Windows Powershell folder as far as I know. So the full path must be hard-coded in the string:

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\')
Let's see what the available methods are for the returned Folder object:

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\') | Get-Member

   TypeName: System.__ComObject#{a7ae5f64-c4d7-4d7f-9307-4d24ee54b841}

Name                       MemberType Definition
----                       ---------- ----------
CopyHere                   Method     void CopyHere (Variant, Variant)
DismissedWebViewBarricade  Method     void DismissedWebViewBarricade ()
GetDetailsOf               Method     string GetDetailsOf (Variant, int)
Items                      Method     FolderItems Items ()
MoveHere                   Method     void MoveHere (Variant, Variant)
NewFolder                  Method     void NewFolder (string, Variant)
ParseName                  Method     FolderItem ParseName (string)
Synchronize                Method     void Synchronize ()
Application                Property   IDispatch Application () {get}
HaveToShowWebViewBarricade Property   bool HaveToShowWebViewBarricade () {get}
OfflineStatus              Property   int OfflineStatus () {get}
Parent                     Property   IDispatch Parent () {get}
ParentFolder               Property   Folder ParentFolder () {get}
Self                       Property   FolderItem Self () {get}
ShowWebViewBarricade       Property   bool ShowWebViewBarricade () {get} {set}
Title                      Property   string Title () {get}
Before any of these methods can be executed, we must use ParseName to create an object that represents an item inside this folder.

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe')

Application  : System.__ComObject
Parent       : System.__ComObject
Name         : powershell.exe
Path         : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
GetLink      :
GetFolder    :
IsLink       : False
IsFolder     : False
IsFileSystem : True
IsBrowsable  : False
ModifyDate   : 27/09/2013 04:13:50
Size         : 471040
Type         : Application
The returned object has four methods:

ExtendedProperty Method     Variant ExtendedProperty (string)
InvokeVerb       Method     void InvokeVerb (Variant)
InvokeVerbEx     Method     void InvokeVerbEx (Variant, Variant)
Verbs            Method     FolderItemVerbs Verbs ()
As you can see there are two ways to call linked verbs: InvokeVerb() and Verbs().

InvokeVerb() is the direct method to work on a FolderItem object. To pin or unpin items to the taskbar it takes as value the verbs as they are described under HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449}.
That's because the action of pinning and unpinning items is managed by the 'Pin to Taskbar' handler which has the very secret CLSID {90AA3A4E-1CBA-4233-B8BB-535773D48449}. The two shown verbs are:

taskbarpin;taskbarunpin


So the code to pin or unpin is the following:

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').invokeverb('TaskbarPin')

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').invokeverb('TaskbarUnPin')
The alternative is to use the Verbs() method. The listed actions match what you would get by right clicking on an item in Windows Explorer:

(New-Object -ComObject shell.application).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').parsename('powershell.exe').verbs() | select Name

Name
----
&Open
Run as &administrator

Scan for Viruses...
Pin to Tas&kbar
Pin to Start Men&u
Restore previous &versions

Cu&t
&Copy
Create &shortcut
&Delete
Rena&me
P&roperties
Please note that the ampersand (&) that appears in some verbs is not a typo. Some verbs actually have the ampersand because it's used for the hot key of the context menu.

The oneliner here is longer than the one that leverages InvokeVerb():

((new-object -com "Shell.Application").Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').Parsename('powershell.exe').Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}).doit()
To make it more readable and understandable, let's split it up:

$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows\System32\WindowsPowershell\v1.0\')
$item = $folder.Parsename('powershell.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}
Now you might be wondering what the 'DoIt' line is for. Well, it's a bit of running in circles but in simple words, DoIt() executes a verb on the FolderItem associated with the verb. Nothing else.
So to resume, the script does the pinning in three steps:
  1. Use the Verbs() method of a FolderItem object to get a list of verbs applicable to the target.
  2. Filter the verbs down to the one with a name matching the expected action.
  3. Execute the DoIt() method to invoke the verb.
When you execute the code above, it's just like Powershell is doing the clicking for you and your pinned apps on the taskbar get saved in both locations below:

In the registry under:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband

In the hidden TaskBar folder:
C:\Users\Username\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
 
Let's end this post with a few information on the Shell.Application. If you want to find out all the possible ComObjects on your PC with Powershell, just check in the registry under HKLM:\Software\Classes. We can perform a bit of pattern matching to filter down to the objects that matches the naming syntax of ComObjects with '^\w+\.\w+$', which can be read like two groups of words separated by a dot.

Here's the code to find them all:

Get-ChildItem HKLM:\Software\Classes -ErrorAction SilentlyContinue | ? {
   $_.PSChildName -match '^\w+\.\w+$' -and (Test-Path -Path "$($_.PSPath)\CLSID")
} | Select-Object -ExpandProperty PSChildName
Shell.Application is one of them:

Get-ChildItem HKLM:\Software\Classes | ? PSChildName -Match 'Shell.Application'

    Hive: HKEY_LOCAL_MACHINE\Software\Classes


Name                           Property
----                           --------
Shell.Application              (default) : Shell Automation Service

Now that we know where it is stored in the Windows Registry, we can also see its CLSID, which is {13709620-C279-11CE-A49E-444553540000}, as you can see opening the key. This value can be used to perform the pinning in another way that I am going to show you.

First of all we have to convert the CLSID to a type, which is done like this:

$Type= [Type]::GetTypeFromCLSID('13709620-C279-11CE-A49E-444553540000')
Then we will leverage the [System.Activator] class, which is generally used to create types of objects. Its CreateInstance method create an instance of the object. This is usually used in .NET Late Binding, as explained here, but I think it's worth having a look:

$Object = [Activator]::CreateInstance($Type)
Then we can proceed like seen before defining the path of the file, its name and the verb to apply:

[Activator]::CreateInstance([Type]::GetTypeFromCLSID('13709620-C279-11CE-A49E-444553540000')).Namespace('C:\Windows\System32\WindowsPowershell\v1.0\').ParseName('powershell.exe').InvokeVerb('taskbarpin')
That's kind of a long one-liner, and unfortunately there is no way of making it shorter as far as I can tell. You should now be able to read it, because you know the basics concepts I have just shown you. I hope you learned something. Stay tuned for more.
Related Posts Plugin for WordPress, Blogger...