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='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}},
 @{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 = @(
 @{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
})|  ? 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

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
  • 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:
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.

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 {
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 {}
                    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 {


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

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

*! Update: 2014-04-08:
Looking for the latest version of ADK 8.1, see my most recent post

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 {
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"
Process {
    $adkGenericURL = (Invoke-WebRequest -Uri -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
        $MainURL = $adkGenericURL.Headers.Location
        $InstallerURLs = DATA {
            ConvertFrom-StringData @'
                0=Toolkit Documentation-x86_en-us.msi
                6=Application Compatibility Toolkit-x86_en-us.msi
                11=Microsoft Compatibility Monitor-x86_en-us.msi
                12=Application Compatibility Toolkit-x64_en-us.msi
                14=Microsoft Compatibility Monitor-x86_en-us.msi
                15=Windows Deployment Tools-x86_en-us.msi
                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
                52=Windows PE x86 x64-x86_en-us.msi
                57=Windows PE x86 x64 wims-x86_en-us.msi
                60=User State Migration Tool-x86_en-us.msi
                63=Volume Activation Management Tool-x86_en-us.msi
                67=WPT Redistributables-x86_en-us.msi
                71=Windows Assessment Toolkit-x86_en-us.msi
                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
                140=Windows Assessment Services-x86_en-us.msi
                144=Windows Assessment Services - Client (Server SKU)-x86_en-us.msi
                147=Windows Assessment Services - Client (AMD64 Architecture Specific, Server SKU)-x86_en-us.msi
                148=Assessments on Server-x86_en-us.msi
                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
        $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

        "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)/$_"
        # 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
            $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
            $newjob = Add-BitsFile -BitsJob $job -Source  $URL -Destination $Destination
        # Begin the download and show us the job
        Resume-BitsTransfer  -BitsJob $job -Asynchronous
        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
         "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 {
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"
        # 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

Replace the command prompt by Powershell in the Win+X menu

Using the GUI, see the following PSTip

and of course you can also achieve this exclusively with Powershell of course 😀

#Requires -version 3.0
#Requires -RunAsAdministrator

Set-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name DontUsePowerShellOnWinX -Value 0 -Type DWord

Get-Process -Name explorer -IncludeUserName | ? Username -match "$env:USERNAME"

Note that the ‘IncludeUserName’ parameter requires elevated user rights.

Fix for Windows Store App Not Loading After Windows 8.1 Upgrade

I like this story, especially because it brings Powershell to the rescue 😎

The first step is to launch a command prompt running as admininstrator by pressing ‘[Windows key]+X’

Then type “powrshell” and hit enter

Or you can do ‘[Windows key]+Q’, type ‘Powershell’, hit ‘Shift+Enter’ and validate by ‘yes’ the User Account control (UAC) prompt.

The second step requires that you type the following two lines of code and hit Enter (see screenshot below)
Note that you can use Powershell completion with the [TAB] key. For example, start typing ‘Add-App’ and hit [TAB]…, idem for parameters, start typing -Pa and hit [TAB]…

Add-AppxPackage -Path $env:SystemRoot\WinStore\AppxManifest.xml -DisableDevelopmentMode -Register

Start-Process "ms-windows-store:"


Having problems with your Surface RT, you can download a recovery image as well as instructions from the download center with the following link:

Producing an Intentional BSOD (Blue Screen of Death)

I came across the following website the other day

Get-process | Stop-Process

..and it reminds me both some key concepts of PowerShell as well as Don Jones and Jeffrey Snover demonstrating cmdlet binding in this video (@25 minutes)

The first concept is “Read Help!”

Get-help Stop-Process -Full

On Windows Vista and later versions of Windows, to stop a process that is not owned by the current user, you must start Windows PowerShell with the “Run as administrator” option. Also, you are prompted for confirmation unless you use the Force parameter.

The help of Powershell confirms that it’s not as straightforward as the above the above blog post assumes.
The above piped cmdlets won’t generate a BSOD because the vast majority of cmdlets that modify the system will ask for confirmation by default.

Of course pressing “A” is a bad idea as it really ends with a BSOD:

The second concept is “when in doubt, use a risk management parameter”.
To simulate and see what these commands would do, you just need to add the -Whatif parameter:

Get-process | Stop-Process -WhatIf

Wait, there’s another crucial thing here. I said

the vast majority of cmdlets that modify the system will ask for confirmation by default

Powershell’s default behavior is controlled by preferences variables.

Get-help about_Preference_Variables

So, if you want the default behavior (and you should really stick to it), don’t alter these preferences variables.

One of the most widely spread error is people starting a script by doing

$ErrorActionPreference = "SilentlyContinue"

Repeat after me, “this is wrong!

Now let’s go back to one of the most important concept of Powershell: CmdletBinding

Jeffrey Snover quickly explained it in the above video I mentioned (~@30 minutes).

Powershell MVP Don Jones has a great nice example to explain it.

Can you figure out what this will do?

Get-Service | Stop-Process -WhatIf

Let’s have Don Jones explain how CmdletBinding works:

ByValue (Plan A)

ByPropertyName (Plan B)

Want to go deeper, here are some insightful links: