Find the owner of a process

Powershell MVP Jeffery Hicks recently mentioned how to get processes that belongs to users:

((Get-Process -IncludeUserName).where({$_.username -AND $_.username -notmatch "^NT"}))

The above one-liner requires both Powershell 4.0 and an elevated PS prompt otherwise you end-up with:


Get-Process : The ‘IncludeUsername’ parameter requires elevated user rights. Try running the command again in a session that has been opened with elevated user rights (that is, Run As Administrator).

To get the current user processes, another one-liner would look like:

ps -IncludeUserName|? UserName -m "$env:USERNAME"

Imagine you’re stuck on Powershell version 3.0 and that you want to get the current user processes.
You can do this with WMI (and mimic the Get-Process console view) like this:

$View = @(
 @{l='Handles';e={$_.HandleCount}},
 @{l='NPM(K)';e={ (Get-Process -Id $_.ProcessId).NonpagedSystemMemorySize/1KB -as [int]}},
 @{l='PM(K)';e={ $_.PrivatePageCount/1KB -as [int]}},
 @{l='WS(K)';e={ $_.WorkingSetSize/1KB -as [int]}},
 @{l='VM(M)';e={ $_.VirtualSize/1mB -as [int]}},
 @{l='CPU(s)';e={ (Get-Process -Id $_.ProcessId).CPU -as [int]}},
 @{l='Id';e={ $_.ProcessId}},
 'UserName'
 @{l='ProcessName';e={ $_.ProcessName}}
)
Get-WmiObject Win32_Process | % { $_ | 
    Add-Member -MemberType ScriptProperty -Name UserName -Value {
        '{0}\{1}' -f $this.GetOwner().Domain,$this.GetOwner().User
    } -Force -PassThru
} | ? UserName -match $env:USERNAME | ft $View -AutoSize

(Ok, I cheated for the NonpagedSystemMemorySize and the CPU load :-P)

…Or by extending Get-Process with WMI like this:

$PSView = @(
 'Handles',
 @{Label="NPM(K)";Expression={[int]($_.NPM/1024)}},
 @{Label="PM(K)";Expression={[int]($_.PM/1024)}},
 @{Label="WS(K)";Expression={[int]($_.WS/1024)}},
 @{Label="VM(M)";Expression={[int]($_.VM/1MB)}},
 @{Label="CPU(s)";Expression={if ($_.CPU -ne $()) { $_.CPU.ToString("N")}}}
 'Id', 'UserName','ProcessName'
 )
Get-Process | % { $_ | 
    Add-Member -MemberType ScriptProperty -Name UserName -Value {
        $o = (Get-WmiObject -Query "Select * FROM Win32_Process WHERE ProcessId = $($this.Id)").GetOwner()
        '{0}\{1}' -f $o.Domain,$o.User 
    } -Force -PassThru
} | ? UserName -match $env:USERNAME |
ft $PSView -AutoSize

I got the above PSView from the Example 5 of the Get-Process Cmdlet help

The interesting new thing in Powershell version 4.0 when you run the Get-Process cmdlet with the new IncludeUserName switch parameter is that you get back a System.Diagnostics.Process#IncludeUserName object and not a System.Diagnostics.Process

This new “child” object (I don’t know how to name it, I’m not a .Net developper) has its custom view defined in C:\Windows\system32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml

It’s may also be very difficult to get this ProcessWithUserName view applied.

Here’s a code that works and has the ProcessWithUserName view applied despite that fact that I omitted the new IncludeUserName switch on purpose

(Get-Process | % { 
    $obj = New-Object -TypeName psobject -ArgumentList $_
    $obj | Add-Member -MemberType ScriptProperty -Name UserName -Value {
        $o = (Get-WmiObject -Query "Select * FROM Win32_Process WHERE ProcessId = $($this.Id)").GetOwner()
        '{0}\{1}' -f $o.Domain,$o.User 
    } -Force -PassThru
    $obj.PSObject.TypeNames.Insert(0,'System.Diagnostics.Process#IncludeUserName')
    $obj
})|  ? UserName -match $env:USERNAME

The parantheses (at the begining and the closing one before piping the whole expression to the where cmdlet) are strictly required, otherwise the process view of the System.Diagnostics applies.

Here’s what is allowed with views and of course, you cannot have an empty UserName column if you’ve omitted the new IncludeUserName switch on purpose

Get-Process | ft -View ProcessWithUserName

Advertisements

Powershell font size

I have recently installed from scratch a vanilla Windows 8.1 Enterprise ISO (GA) on a Surface Pro but when I launched Powershell by typing ‘Powershell’ in the Start Menu, I got this 😦

Ugly isn’t it. The fonts size is just 4×6 which is unreadable compared to the old command prompt that you can see in the background.

I’ve made the whole installation more than once and tried different local accounts to make sure the above ugly interface I got was persistent across users and installations of Windows. In other words I’m able reproduce the issue to start figuring out what’s going on.

Then I started looking at what Powershell itself can do to fix my problem. I thought I would just edit one of the Powershell profiles and set the font size there.
I explored the $host variable, $host.RAW.UI as well as the [system.console] .Net class but got disappointed. There’s no easy way to change the font size, like I’d change the BufferSize, the WindowTitle or the BackgroundColor.

As plan A failed, I switched to plan B which is basically ‘when in doubt, run process monitor’.

Here’s what I discovered, I’ll try to keep it short.

When I press [Win+Q] and type “Powershell”, the search proposes a link to the shortcut file that is located in:
“C:\ProgramData\Microsoft\Windows\Start Menu\Programs\System Tools\Windows Powershell.lnk”

Notice that the result of the search is named “Windows Powershell”.

If I execute it, it reads this shortcut file (.lnk) and launches its target.
To be more accurrate, it reads it ExtraData block of the shortcut file which holds the ‘ConsoleDataBlock‘ that inventories all the display settings such as the font size.

If I right-click the lnk file, it appears to be somehow “corrupted”:

Now, if I rename the shortcut file’s extension, the search being performed by [Win]+Q doesn’t find it and reaches powershell.exe located in C:\Windows\system32\WindowsPowerShell\v1.0\, which itself is a hardlink to a powershell.exe file in the WinSXS folder.

Now notice that the result of the search is no longer named “Windows Powershell” but just “Powershell”:

Then Process Monitor aka ProcMon also reveals that it reads first the FontSize value under the HKEY_CURRENT_USER\Console key and then complains that it doesn’t find a font size value under the HKCU\Console\%systemroot%_System32_WindowsPowershell_v1.0_powershell.exe key.

There are 4 solutions:

  • Open a support call to fix my current issue and/or fill-in a suggestion on connect.microsoft.com
  • Use the SetConsoleFont Module marked as compatible with XP on the technet gallery that has these caveats
  • Deploy a shortcut file that has the correct font size
  • Delete the shortcut file and write the FontSize value in the registry

When was the last time that VM was powered on?

It seems that VMWare ESX PowerCli has a nice cmdlet called Get-LastPowerOn to answer that question:
https://blogs.vmware.com/vipowershell/2009/10/when-was-the-last-time-that-vm-was-powered-on.html
and that you can smoothly do:

Get-VM | Get-LastPowerOn

This question was raised for Hyper-V in December 2012 and didn’t get a correct answer yet.
http://stackoverflow.com/questions/13873575/how-can-i-know-the-hyper-v-vms-last-poweroff-time

Even if Windows Server 2012 R2 hit GA a few days ago and has 14 new cmdlets in its Hyper-V module compared to its predecessor Server 2012…


(I’m digressing a little bit but here are the new cmdlets:

)… it doesn’t have a nice Get-VMLastPowerOn cmdlet yet 😦

No problem, Powershell to the rescue 😀

#Requires -version 3.0
Function Get-VMStateHistory {
[CmdletBinding()]
Param(
 [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
 [string[]]$ComputerName=$env:COMPUTERNAME
)
Begin {
    $HT = @{
        LogName = 'Microsoft-Windows-Hyper-V-Worker-Admin' ;
        Id = 18500,18502,18504,18508,18510,18512,18514,18516,18518,18596
    }
}
Process {
    foreach ($Computer in $ComputerName) {
        try {
            Get-WinEvent -FilterHashtable $HT -ComputerName $Computer -ErrorAction Stop | ForEach-Object {
                $obj = Switch ($_.Id) {
                    18500 { @{ Action = 'Started'             ; TurnedOn = $true } ; break}
                    18502 { @{ Action = 'Turned Off'          ; TurnedOn = $false} ; break}
                    18504 { @{ Action = 'Shutdown (by host)'  ; TurnedOn = $false} ; break}
                    18508 { @{ Action = 'Shutdown (by guest)' ; TurnedOn = $false} ; break}
                    18510 { @{ Action = 'Saved'               ; TurnedOn = $false} ; break}
                    18512 { @{ Action = 'Reset (by host)'     ; TurnedOn = $true } ; break}
                    18514 { @{ Action = 'Reset (by guest)'    ; TurnedOn = $true } ; break}
                    18516 { @{ Action = 'Paused'              ; TurnedOn = $false} ; break}
                    18518 { @{ Action = 'Resumed'             ; TurnedOn = $true } ; break}
                    18596 { @{ Action = 'Restored'            ; TurnedOn = $true } ; break}
                    default {}
                }        
                [pscustomobject]@{
                    VMName = $_.Properties[0].Value
                    TurnedOn = $obj['TurnedOn']
                    Action = $obj['Action']
                    Time = $_.TimeCreated
                }
            } 
        } catch {
            Write-Warning -Message "Failed to get events from $Computer because $($_.Exception.Message)"
        }
    }
}
End {}
} 

Function Get-VMLastPowerOn {
[CmdletBinding()]
Param(
 [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
 [alias("VMName")]
 [string[]]$Name,

 [Parameter(ValueFromPipelineByPropertyName)]
 [string[]]$ComputerName
)

Begin {
    if ($ComputerName) {
        $VMStatesEvents = Get-VMStateHistory -ComputerName $ComputerName| ? TurnedOn
    } else {
        $VMStatesEvents = Get-VMStateHistory | ? TurnedOn
    }
}
Process {
    Switch (([int]$PSBoundParameters.ContainsKey('Name'))) {
        1 {
            foreach ($VM in $Name) {
                $VMStatesEvents | ? VMName -Like $VM | Sort-Object -Descending -Property Time | Select -First 1
            }
        }
        0 {
            foreach ($VM in (($VMStatesEvents).VMName | Sort-Object -Unique)) {
                $VMStatesEvents | ? VMName -Like $VM | Sort-Object -Descending -Property Time | Select -First 1
            }
        }
    }
}
End{}
}

I wish I had more time and I known I should have written the help assiociated with the 2 cmdlets. Instead here’s how to use it:

Case 1: Get the last power on time of an specific VM passed as parameter

Get-VMLastPowerOn -Name MyVMName | ft -AutoSize

Case 2: List last power on info for all VMs on the current host

Get-VMLastPowerOn | ft -AutoSize

Case 3: Pipe the original Get-VM cmdlet of the Hyper-V module and Get-VMLastPowerOn.

Get-VM | ? Name -match "R" |             
Get-VMLastPowerOn -ComputerName localhost

Did I already say that Powershell rocks 😎

Bonus: You may have noticed I chose specific event Ids. There are more on Windows 2012 R2 (18594 = fast-saved and 18592 = fast-restored, for example)
Here’s how I made up my mind and chose the above specific Ids:

(Get-WinEvent -ListProvider Microsoft-Windows-Hyper-V-Worker).Events |            
? id -match "^185" |ft id,Description -AutoSize

Download Windows Assessment and Deployment Kit (Windows ADK) 8.1

*! Update: 2014-09-30:
Looking for the latest version of ADK 8.1, see my most recent post
https://p0w3rsh3ll.wordpress.com/2014/09/30/follow-up-on-downloading-windows-assessment-and-deployment-kit-windows-adk-8-1-september-2014/

*! Update: 2014-04-08:
Looking for the latest version of ADK 8.1, see my most recent post
https://p0w3rsh3ll.wordpress.com/2014/04/08/follow-up-on-downloading-windows-assessment-and-deployment-kit-windows-adk-8-1/

If you’ve got like me the version 8.100.25984, you’ve got the RTM version of ADK 8.1

You may have missed that the ADK has been updated with GA bits:

To obtain the latest version, I’ve updated the function below 😀
If you’re still on Powershell version 3.0, you can still copy and use the function, it works. Just be aware that you need admin rights for it to work.

#Requires -Version 4
#Requires -RunAsAdministrator 
 
Function Get-ADKFiles {
[CmdletBinding()]    
param(
    [parameter(Mandatory)]
    [system.string]$TargetFolder
)
Begin {
    $HT = @{}
    $HT += @{ ErrorAction = 'Stop'}
    # Validate target folder
    try {
        Get-Item $TargetFolder @HT | Out-Null
    } catch {
        Write-Warning -Message "The target folder specified as parameter does not exist"
        break
    }
}
 
Process {
    $adkGenericURL = (Invoke-WebRequest -Uri http://go.microsoft.com/fwlink/?LinkID=313081 -MaximumRedirection 0 -ErrorAction SilentlyContinue)
    # There's an expected error saying:
    # The maximum redirection count has been exceeded. 
    # To increase the number of redirections allowed, supply a higher value to the -MaximumRedirection parameter.
 
    # 302 = redirect as moved temporarily
    if ($adkGenericURL.StatusCode -eq 302) {
     
        # Currently set to http://download.microsoft.com/download/6/A/E/6AEA92B0-A412-4622-983E-5B305D2EBE56/adk/
        $MainURL = $adkGenericURL.Headers.Location
 
        $InstallerURLs = DATA {
            ConvertFrom-StringData @'
                0=Toolkit Documentation-x86_en-us.msi
                1=e5f4f4dc519b35948be4500a7dfeab14.cab
                2=d562ae79e25b943d03fc6aa7a65f9b81.cab
                3=56e5d88e2c299be31ce4fc4a604cede4.cab
                4=4fc82a5cedaab58e43b487c17f6ef6f3.cab
                5=dotNetFx45_Full_x86_x64.exe
                6=Application Compatibility Toolkit-x86_en-us.msi
                7=3d610ba2a5a333717eea5f9db277718c.cab
                8=f7699e5a82dcf6476e5ed2d8a3507ace.cab
                9=e65f08c56c86f4e6d7e9358fa99c4c97.cab
                10=0a3a39d2f8a258e1dea4e76da0ec31b8.cab
                11=Microsoft Compatibility Monitor-x86_en-us.msi
                12=Application Compatibility Toolkit-x64_en-us.msi
                13=83bd1072721871ea0bdc4fab780d9382.cab
                14=Microsoft Compatibility Monitor-x86_en-us.msi
                15=Windows Deployment Tools-x86_en-us.msi
                16=bbf55224a0290f00676ddc410f004498.cab
                17=b6758178d78e2a03e1d692660ec642bd.cab
                18=f2a850bce4500b85f37a8aaa71cbb674.cab
                19=a011a13d3157dae2dbdaa7090daa6acb.cab
                20=9050f238beb90c3f2db4a387654fec4b.cab
                21=f480ed0b7d2f1676b4c1d5fc82dd7420.cab
                22=2517aec0259281507bfb693d7d136f30.cab
                23=8624feeaa6661d6216b5f27da0e30f65.cab
                24=eacac0698d5fa03569c86b25f90113b5.cab
                25=630e2d20d5f2abcc3403b1d7783db037.cab
                26=d2611745022d67cf9a7703eb131ca487.cab
                27=a7eb3390a15bcd2c80a978c75f2dcc4f.cab
                28=5d984200acbde182fd99cbfbe9bad133.cab
                29=1439dbcbd472f531c37a149237b300fc.cab
                30=69f8595b00cf4081c2ecc89420610cbd.cab
                31=413a073d16688e177d7536cd2a64eb43.cab
                32=ea9c0c38594fd7df374ddfc620f4a1fd.cab
                33=3b71855dfae6a44ab353293c119908b8.cab
                34=0c48c56ca00155f992c30167beb8f23d.cab
                35=662ea66cc7061f8b841891eae8e3a67c.cab
                36=36e3c2de16bbebad20daec133c22acb1.cab
                37=870d7f92116bc55f7f72e7a9f5d5d6e1.cab
                38=500e0afd7cc09e1e1d6daca01bc67430.cab
                39=56dd07dea070851064af5d29cadfac56.cab
                40=fcc051e0d61320c78cac9fe4ad56a2a2.cab
                41=c98a0a5b63e591b7568b5f66d64dc335.cab
                42=abbeaf25720d61b6b6339ada72bdd038.cab
                43=Windows System Image Manager on amd64-x86_en-us.msi
                44=Windows System Image Manager on x86-x86_en-us.msi
                45=Windows Deployment Customizations-x86_en-us.msi
                46=377a2b6b26ea305c924c25cf942400d6.cab
                47=93ed81ef8cf2e77c6ebc8aba5d95b9cf.cab
                48=4e56c6c11e546d4265da4e9ff7686b67.cab
                49=WimMountAdkSetupX86.exe
                50=WimMountAdkSetupAmd64.exe
                51=WimMountAdkSetupArm.exe
                52=Windows PE x86 x64-x86_en-us.msi
                53=a32918368eba6a062aaaaf73e3618131.cab
                54=9722214af0ab8aa9dffb6cfdafd937b7.cab
                55=aa25d18a5fcce134b0b89fb003ec99ff.cab
                56=0b63b7c537782729483bff2d64a620fa.cab
                57=Windows PE x86 x64 wims-x86_en-us.msi
                58=6d3c63e785ac9ac618ae3f1416062098.cab
                59=690b8ac88bc08254d351654d56805aea.cab
                60=User State Migration Tool-x86_en-us.msi
                61=5ac1863798809c64e85c2535a27a3da6.cab
                62=4d2878f43060bacefdd6379f2dae89b0.cab
                63=Volume Activation Management Tool-x86_en-us.msi
                64=fe43ba83b8d1e88cc4f4bfeac0850c6c.cab
                65=WPTx86-x86_en-us.msi
                66=WPTx64-x86_en-us.msi
                67=WPT Redistributables-x86_en-us.msi
                68=WPTx86-x86_en-us.msi
                69=WPTx64-x86_en-us.msi
                70=WPTarm-arm_en-us.msi
                71=Windows Assessment Toolkit-x86_en-us.msi
                72=0ce2876e9da7f82aac8755701aecfa64.cab
                73=24b9e5f1f97c2f05aa95ee1f671fd3cc.cab
                74=23ca402f61cda3f672b3081da79dab63.cab
                75=Windows Assessment Toolkit (AMD64 Architecture Specific)-x86_en-us.msi
                76=Windows Assessment Toolkit (X86 Architecture Specific)-x86_en-us.msi
                77=Assessments on Client-x86_en-us.msi
                78=b3892d561b571a5b8c81d33fbe2d6d24.cab
                79=3611bd81544efa3deb061718f15aee0c.cab
                80=ed711e0a0102f1716cc073671804eb4c.cab
                81=57007192b3b38fcd019eb88b021e21cc.cab
                82=450f8c76ee138b1d53befd91b735652b.cab
                83=f8f7800500b180b8a2103c40ce94f56a.cab
                84=77adc85e5c49bbd36a91bb751dc55b39.cab
                85=d519967dbb262c80060d9efb5079aa23.cab
                86=bd748d6fbff59b2a58cebdb99c3c6747.cab
                87=3585b51691616d290315769bec85eb6f.cab
                88=fa7c072a4c8f9cf0f901146213ebbce7.cab
                89=cfb8342932e6752026b63046a8d93845.cab
                90=527b957c06e68ebb115b41004f8e3ad0.cab
                91=a03686381bcfa98a14e9c579f7784def.cab
                92=cd23bfdfd9e3dfa8475bf59c2c5d6901.cab
                93=ab3291752bc7a02f158066789e9b0c03.cab
                94=6bdcd388323175da70d836a25654aa92.cab
                95=a1d26d38d4197f7873a8da3a26fc351c.cab
                96=6da2af86cb1227e66cf9bc85f2786782.cab
                97=625aa8d1c0d2b6e8cf41c50b53868ecd.cab
                98=bc1fef9daa903321722c08ce3cf51261.cab
                99=86ae476dfe0498a5b5d1b6f3076412c7.cab
                100=a565f18707816c0d052281154b768ac0.cab
                101=732eefaf52275b7a708311a31c82c814.cab
                102=6dc62760f8235e462db8f91f6eaa1d90.cab
                103=fbcf182748fd71a49becc8bb8d87ba92.cab
                104=1f90b0f7321fab8dcdedaba3b30415f3.cab
                105=c0f42c479da796da513cc5592f0759d3.cab
                106=6d2cfb2c5343c33c8d9e54e7d1f613f9.cab
                107=0302dc615b0a5fd4810430b2cdacb5e3.cab
                108=781e7c95c1b6b277057c9b53b7b5a044.cab
                109=036c618de505eeb40cca35afad6264f5.cab
                110=7ab29d7f105f1e7814198f23b60f8e5d.cab
                111=d5abe4833b23e13dc7038bde9c525069.cab
                112=aa4db181ead2227e76a3d291da71a672.cab
                113=b0189bdfbad208b3ac765f88f21a89df.cab
                114=38d93b8047d5efb04cf01ab7ec66d090.cab
                115=3dc1ed76e5648b575ed559e37a1052f0.cab
                116=3814eaa1d4e897c02ac4ca93e7e7796a.cab
                117=3814eaa1d4e897c02ac4ca93e7e7796a.cab
                118=17c9d60f2bc5bc54c58782d614afcbf0.cab
                119=125b1c8c81e36ec9dbe5abf370ff9919.cab
                120=be7ebc1ac434ead4ab1cf36e3921b70e.cab
                121=268b1a41f6bd2906449944b964bf7393.cab
                122=b5227bb68c3d4641d71b769e3ac606a1.cab
                123=7c11b295fb7f25c6d684b1957e96a226.cab
                124=94cae441bc5628e21814208a973bbb9d.cab
                125=a30d7a714f70ca6aa1a76302010d7914.cab
                126=45c632fb53b95fe3bd58a6242325afa6.cab
                127=0d981f062236baed075df3f42b1747db.cab
                128=4d15138ec839ce36f5b68c16b332920a.cab
                129=7011bf2f8f7f2df2fdd2ed7c82053d7f.cab
                130=0708be5ffbe332f6a1571c929c1322a5.cab
                131=c6babfeb2e1e6f814e70cacb52a0f923.cab
                132=39837d43d71c401e7edc9ba3e569cd69.cab
                133=3eaef6a740a72a55f4a0ac3039d05419.cab
                134=11bdc4a4637c4d7ab86107fd13dcb9c6.cab
                135=8c27542f7954c25af62730fbb1e211d2.cab
                136=1620efa4ffe2a6563530bd0158b17fe6.cab
                137=fd5778f772c39c09c3dd8cd99e7f0543.cab
                138=ac9ff098e23012b74624db792b538132.cab
                139=SQLEXPR_x86_ENU.exe
                140=Windows Assessment Services-x86_en-us.msi
                141=7c195d91008a0a6ad16e535ac228467d.cab
                142=6894c1e1e549c4ab533078e3ff2e92af.cab
                143=eebe1a56de59fd5a57e26205ff825f33.cab
                144=Windows Assessment Services - Client (Server SKU)-x86_en-us.msi
                145=18e5e442fc73caa309725c0a69394a46.cab
                146=5775a15b7f297f3e705a74609cb21bbc.cab
                147=Windows Assessment Services - Client (AMD64 Architecture Specific, Server SKU)-x86_en-us.msi
                148=Assessments on Server-x86_en-us.msi
                149=wasinstaller.exe
                150=Windows Assessment Services - Client (Client SKU)-x86_en-us.msi
                151=Windows Assessment Services - Client (X86 Architecture Specific, Client SKU)-x86_en-us.msi
                152=Windows Assessment Services - Client (AMD64 Architecture Specific, Client SKU)-x86_en-us.msi
                153=Kits Configuration Installer-x86_en-us.msi
                154=388dee738d7d1c99d6fe776a85ee32f8.cab
                155=4defb086385752d8cd0d1432900fb4ca.cab
                156=InstallRegHiveRecoveryDriverAmd64.exe
                157=InstallRegHiveRecoveryDriverX86.exe
'@
        }
 
        $PatchesURLs = DATA {
            ConvertFrom-StringData @'
                0=Application Compatibility Toolkit-x64_en-us.msp
                1=Application Compatibility Toolkit-x86_en-us.msp
                2=Assessments on Client-x86_en-us.msp
                3=Assessments on Server-x86_en-us.msp
                4=User State Migration Tool-x86_en-us.msp
                5=Volume Activation Management Tool-x86_en-us.msp
                6=Windows Assessment Services-x86_en-us.msp
                7=Windows Assessment Toolkit-x86_en-us.msp
                8=Windows Deployment Tools-x86_en-us.msp
                9=Windows PE x86 x64-x86_en-us.msp
                10=Windows System Image Manager on amd64-x86_en-us.msp
                11=Windows System Image Manager on x86-x86_en-us.msp
                12=WPT Redistributables-x86_en-us.msp
                13=WPTx64-x86_en-us.msp
                14=WPTx86-x86_en-us.msp
'@
        }

        "Installers","Patches\8.100.26020" | ForEach-Object -Process {
            # Create target folders if required as BIT doesn't accept missing folders
            If (-not(Test-Path (Join-Path -Path $TargetFolder -ChildPath $_))) {
                try {
                    New-Item -Path (Join-Path -Path $TargetFolder -ChildPath $_) -ItemType Directory -Force @HT
                } catch {
                    Write-Warning -Message "Failed to create folder $($TargetFolder)/$_"
                    break
                }
            }
        }
        # Get adksetup.exe
        Invoke-WebRequest -Uri "$($MainURL)adksetup.exe" -OutFile  "$($TargetFolder)\adksetup.exe"
 
        # Create an job that will downlad our first file
        $job = Start-BitsTransfer -Suspended -Source "$($MainURL)Installers/$($InstallerURLs['0'])" -Asynchronous -Destination (Join-Path -Path $TargetFolder -ChildPath ("Installers/$($InstallerURLs['0'])")) 
         
        # Downlod installers
        For ($i = 1 ; $i -lt $InstallerURLs.Count ; $i++) {
            $URL = $Destination = $null
            $URL = "$($MainURL)Installers/$($InstallerURLs[$i.ToString()])"
            $Destination = Join-Path -Path (Join-Path -Path $TargetFolder -ChildPath Installers) -ChildPath (([URI]$URL).Segments[-1] -replace '%20'," ")
            # Add-BitsFile http://technet.microsoft.com/en-us/library/dd819411.aspx
            $newjob = Add-BitsFile -BitsJob $job -Source  $URL -Destination $Destination
            Write-Progress -Activity "Adding file $($newjob.FilesTotal)" -Status "Percent completed: " -PercentComplete (($newjob.FilesTotal)*100/($InstallerURLs.Count))
        }

        # Donwload Patches
        For ($i = 0 ; $i -lt $PatchesURLs.Count ; $i++) {
            $URL = $Destination = $null
            $URL = "$($MainURL)Patches/8.100.26020/$($PatchesURLs[$i.ToString()])"
            $Destination = Join-Path -Path (Join-Path -Path $TargetFolder -ChildPath "Patches/8.100.26020") -ChildPath (([URI]$URL).Segments[-1] -replace '%20'," ")
            # Add-BitsFile http://technet.microsoft.com/en-us/library/dd819411.aspx
            $newjob = Add-BitsFile -BitsJob $job -Source  $URL -Destination $Destination
        }
 
        # Begin the download and show us the job
        Resume-BitsTransfer  -BitsJob $job -Asynchronous
 
        # http://msdn.microsoft.com/en-us/library/windows/desktop/ee663885%28v=vs.85%29.aspx
        while ($job.JobState -in @('Connecting','Transferring','Queued')) {
            Write-Progress -activity "Downloading ADK files" -Status "Percent completed: " -PercentComplete ($job.BytesTransferred*100/$job.BytesTotal)
        } 
        Switch($job.JobState) {
         "Transferred" {
            Complete-BitsTransfer -BitsJob $job
            break
        }
         "Error" {
            # List the errors.
            $job | Format-List
        } 
        default {
            # Perform corrective action.
        } 
        }
    }
}
End {}
}

One-liner to determine daylight saving time

Powershell MVP Mike F Robbins, the winner of the Scripting Games 2013 posted the following function to determine daylight saving time.

As he said, this is only valid in the US. I wondered what it would look like in my time zone.

I knew there’s an official page that uses a registry based approach on this page kb914387
I also knew that there are .Net classes that can be used, see for example this post

I didn’t know how to read the TZI (time zone information) from the registry until I landed on the following MSDN page

Reading the registry seems complicated and there’s actually a buried .Net method for this purpose.

2007..2019 | % { [System.TimeZone]::CurrentTimeZone.GetDaylightChanges($_) }

In my timezone, it look likes 😀

Follow-up on Active Directory schema versions

I published last year a function that helps determine the current Active Directory, Exchange Server and Lync schema version on this page.

Now that Windows 2012 Server R2 reached its general availability (GA), my function needs to be updated 😀

I’ve added the version for Windows 2012 R2, Exchange Server 2010 SP3.
I’ve removed to the code for loading the Active Directory module in the begin block and prefered to rely on the #requires statements for this purpose.

#Requires -Version 3.0
#Requires -Module ActiveDirectory

Function Get-ADSchemaVersion {
[CmdletBinding()]
param()
Begin {
    $KnownSchema = DATA {
    ConvertFrom-StringData @'
    13=Windows 2000 Server
    30=Windows Server 2003
    31=Windows Server 2003 R2
    44=Windows Server 2008
    47=Windows Server 2008 R2 
    56=Windows Server 2012 RTM
    69=Windows Server 2012 R2
    4397=Exchange Server 2000 RTM
    4406=Exchange Server 2000 SP3
    6870=Exchange Server 2003 RTM
    6936=Exchange Server 2003 SP3
    10628=Exchange Server 2007 RTM
    10637=Exchange Server 2007 RTM
    11116=Exchange 2007 SP1
    14622=Exchange 2007 SP2 or Exchange 2010 RTM
    14726=Exchange 2010 SP1
    14732=Exchange 2010 SP2
    14734=Exchange 2010 SP3
    15137=Exchange 2013 RTM
    1006=LCS 2005
    1007=OCS 2007 R1
    1008=OCS 2007 R2
    1100=Lync Server 2010
    1150=Lync Server 2013
'@
    }    
}
Process {
    try {
        $SchemaPartition = (Get-ADRootDSE -ErrorAction Stop).NamingContexts | Where-Object {$_ -like "*Schema*"} 
    } catch {
        Write-Warning -Message "Failed to find the AD naming context"
    }
    if ($SchemaPartition) {

        # Get the version of AD schema
        try {
            $SchemaVersionAD = (Get-ADObject $SchemaPartition -Property objectVersion -ErrorAction Stop).objectVersion
            New-Object -TypeName psobject -Property @{
                ProductName = "Active Directory"
                Version = $SchemaVersionAD
                Description = $KnownSchema[$SchemaVersionAD.ToString()]
            }
        } catch {
            Write-Warning -Message "Failed to query the AD schema version"
            break
        }
        # Get the version of Exchange
        try {
            $SchemaVersionExchange = (Get-ADObject "CN=ms-Exch-Schema-Version-Pt,$SchemaPartition" -Property rangeUpper -ErrorAction Stop).rangeUpper
            New-Object -TypeName psobject -Property @{
                ProductName = "Exchange"
                Version = $SchemaVersionExchange
                Description = $KnownSchema[$SchemaVersionExchange.ToString()]
            }
        } catch {
            Write-Warning -Message "Schema version for Exchange not found"
        }

        # Get the version of Lync
        try {
            $SchemaVersionLync = (Get-ADObject "CN=ms-RTC-SIP-SchemaVersion,$SchemaPartition" -Property rangeUpper -ErrorAction Stop).rangeUpper
            New-Object -TypeName psobject -Property @{
                ProductName = "Lync"
                Version = $SchemaVersionLync
                Description = $KnownSchema[$SchemaVersionLync.ToString()]
            }
        } catch {
            Write-Warning -Message "Schema version for Lync not found"
        }
  
    }
}
End {}
}

Get-ADSchemaVersion | Select ProductName,Version,Description | ft -AutoSize

Powershell version loading on Windows 8.1 / 2012 R2

Yesterday, I used the new -Runasadministrator #requires statement and I failed to mention that it’s only available on Powershell version 4.0.

Today, I’ve tried to determine how bad I failed and actually learned something new.

I’ve got surprised by the fact that the PSversion is showing 4.0 although I launched Powershell using a specific version (3.0 in my case).

I did the same with version 2.0 and I got the following message that was expected.

I added the missing component with the following command:

Enable-WindowsOptionalFeature -Online -FeatureName 'NetFx3'

Now, version 2.0 is loaded

Why was powershell loading version 4.0 although I specified 3.0?

Well the answer can be found in the release notes of the preview