About Me · Send mail to the author(s) E-mail · Twitter

At GROSSWEBER we practice what we preach. We offer trainings for modern software technologies like Behavior Driven Development, Clean Code and Git. Our staff is fluent in a variety of languages, including English.

Feed Icon


Open Source Projects


Blogs of friends

Now playing [?]

Error retrieving information from external service.


Previous Page Page 2 of 2 in the PowerShell category

Enumerating Foreign Interactive Sessions in Powershell

Posted in PowerShell at Friday, 13 April 2007 17:40 W. Europe Daylight Time

A reader asked if it is possible to get all interactive sessions not created by the current user (i.e. the Administrator) in Powershell. Since I had done something similar in my Terminal Server logon script some time ago I thought this would be pretty easy to achieve. Again, I'm heavily relying on WMI. Yet, handling the "flat" (in an OO sense) WMI objects mostly containing string properties doesn't seem right. Here's what I came up with:

# Prints the number of interactive sessions not created by the current user.

$CurrentUserFragment = "*Domain=`"" + $Env:UserDomain.Trim() + "`",Name=`"" + $Env:Username.Trim() + "`""

# Get all interactive sessions (including Terminal Server sessions).
$InteractiveSessions = Get-WmiObject Win32_LogonSession | Where-Object {
    $_.LogonType -eq 2 -or $_.LogonType -eq 10

$ForeignSessions = 0
foreach($Session in $InteractiveSessions) {
    $SessionIdFragment = "*`"" + $Session.LogonId + "`""
    # Test if the session is established by the current user.
    $ForeignSession = Get-WmiObject Win32_LoggedOnUser | Where-Object {
        $_.Antecedent -inotlike $CurrentUserFragment -and $_.Dependent -ilike $SessionIdFragment
    } | Measure-Object
    $ForeignSessions += $ForeignSession.Count

Write-Host $ForeignSessions "Foreign Interactive Session(s)"

This command basically mimics this UNIX command supplied by the reader¹:

who | grep -V "root" | wc -l

The Powershell script is not as concise as I would like it to be and presumably could be optimized. My quick hack should be wrapped in a Cmdlet if you plan to use it in a professional environment.

¹ I have no idea if the script works because I didn't test it. 

Now Playing [?]: Thievery CorporationThe Cosmic GameDoors of perception (feat. Gunjan)

Sorting Visual Studio's "Add New Item" Dialog

Posted in PowerShell | Visual Studio at Saturday, 03 March 2007 18:33 W. Europe Standard Time

While browsing my ever increasing stack of unread The Daily Grind posts I came across a blog post by K. Scott Allen that describes how to sort Visual Studio's "Add New Item" dialog with PowerShell. Neat idea, as I always disliked the fact that Visual Studio has no way of sorting these items alphabetically but relies on a property inside vstemplate files.

Visual Studio Default Sort Order

After downloading and running the PowerShell script he provides on my machine, I was greeted with some error messages. Some WinFX templates were destroyed, as these have an App_Code subfolder inside the zip that the Scott's script doesn't handle well. Fortunately the script creates backups so it wasn't a dealbreaker but rather a point to start from revisiting the script and making it more compatible.

# sort-vsItems
# scott at OdeToCode dot com
# Modified by AlexanderGross at gmx dot de
# Use at your own risk!
# Script will make a backup of each template just in case.
# vjslib for .zip support.
[System.Reflection.Assembly]::Load("vjslib, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") | out-null
# Get list of VS installed templates.
$installDir = [System.IO.Path]::Combine((gp HKLM:Software\Microsoft\VisualStudio\8.0).InstallDir, "ItemTemplates")
$templateFiles = gci -recurse $installDir | ? {$_.extension -eq ".zip"}
# Append list of custom templates.
$installDir = [System.IO.Path]::Combine((gp "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders").Personal, "Visual Studio 2005\Templates\ItemTemplates")
$templateFiles += gci -recurse $installDir | ? {$_.extension -eq ".zip"}   # Sort all templates by filename. $templateFiles = $templateFiles | sort name   $i = 1 $count = 0 $tmpDir = new-item ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())) -type directory $buffer = new-object System.SByte[] (8192)   # Iterate through all template files. foreach($templateFile in $templateFiles) {     write-host "Processing" $templateFile.FullName       # Extract all files (no methods available to modify zip in place).     $zip = new-object$templateFile.FullName)     $entries = $zip.entries()     while($entries.hasMoreElements())     {         $zipEntry = $entries.nextElement()           # Ensure output directory exists.         $filename = [System.IO.Path]::Combine($tmpDir.FullName, $zipEntry.getName())           # $zipEntry.isDirectory() does not work for Microsoft zips (e.g. Web\CSharp\         $directory = [System.IO.Path]::GetDirectoryName($filename)         if ([System.IO.Directory]::Exists($directory) -ne $true)         {             mkdir $directory | out-null               # In case the zip tells us it's a directory entry, skip the entry to prevent exceptions.             if ($zipEntry.isDirectory())             {                 continue             }         }           $in = $zip.getInputStream($zipEntry)         $out = new-object$filename)           while(($count = $$buffer, 0, $buffer.Count)) -gt 0)         {             $out.write($buffer, 0, $count)         }           $out.Close()         $in.Close()     }     $zip.Close()       # Tweak the sort order element.     $vst = gci -recurse $tmpDir | ? {$_.extension -eq ".vstemplate"}     if ($vst -eq $null)     {         # The zip file does not contain a vstemplate.           # Clean temporary directory for the next template file.         del $tmpDir\* -force -recurse         continue     }       $xmlDoc = new-object System.Xml.XmlDocument     $xmlDoc.Load($vst.FullName)     if ($xmlDoc.VSTemplate.TemplateData.SortOrder -ne $null)     {         # Sort by zip name. Sort order must be a multiple of 10.         $xmlDoc.VSTemplate.TemplateData.SortOrder = ($i++ * 10).ToString()           # Sort by item name in Visual Studio.         # Uncomment this line if you want to let Visual Studio sort by item name.         # $xmlDoc.VSTemplate.TemplateData.SortOrder = "10"           $xmlDoc.Save($vst.FullName)     }       # Backup existing zip file.     $backupName = $templateFile.FullName + ".bak"     if(test-path $backupName)     {         # Remove previous backups.         remove-item $backupName     }     move-item $templateFile.FullName $backupName       # Zip up modified version.     $zip = new-object$templateFile.FullName))     $files = gci -recurse $tmpDir     foreach($file in $files)     {         if ($file.Attributes -contains "Directory")         {             # Subfolders are created automatically with files residing in subfolders.             continue         }           # Create a file entry.         # Replacing the last backslash from $tmpDir.FullName is crucial, the zips would work with any other         # zip editor but Visual Studio doesn't like files with a leading backslash (though one doesn't see         # it in WinZip).         $zipEntry = new-object$file.FullName.Replace($tmpDir.FullName + "\", ""))         $zip.putNextEntry($zipEntry)         $in = new-object$file.FullName)         while(($count = $$buffer, 0, $buffer.Count)) -gt 0)         {             $zip.write($buffer, 0, $count)         }         $in.close()         $zip.closeEntry()       }     $zip.close()       # Clean temporary directory for the next template file.     del $tmpDir\* -force -recurse }   del $tmpDir -force -recurse   write-host "Running Visual Studio to refresh templates" $vstudio = (gp HKLM:Software\Microsoft\VisualStudio\8.0).InstallDir & $vstudio\devenv /setup

DownloadDownload the updated script.

Please note the part highlighted in red: If you leave that last red line commented, the item template entries will be sorted based on the zip file name. Just like Scott's original script did. As you can see in the screenshot below, this will get you some pseudo-grouping as all WinFX templates are at the bottom.

Sorted by Zip File Name

If you want to let Visual Studio do the sorting, uncomment the last red line and you'll get truly sorted item templates.

Sorted by Item Name

There's also another solution to the problem, but FixVsItemSortOrder has a bug that prevents "<Language>ProjectItems" folders from being sorted (so says the readme). Not sure what he means there because there's no ProjectItems folder, maybe he means the ProjectTemplates folder. FixVsItemSortOrder, in contrast to the PowerShell script above, also doesn't take user-specific item templates into account.

Now Playing [?]: AirThe Virgin Suicides O.S.T. – Bathroom girl

How to Disallow Multiple Terminal Server Sessions Using a Logon Script

Posted in PowerShell at Thursday, 29 June 2006 20:02 W. Europe Daylight Time

Remote DesktopIf you're working with Microsoft's Terminal Services, you may know about the "Force single session" option. Essentially, turning this option on forces Terminal Services to allow just one session per user. If user Alice is logged on and another users authenticates as Alice, Alice's first session would be disconnected and taken over by "the other" Alice. Concurrent sessions of Alice and Bob are not affected, that's what Terminal Services are for.

The "Force single session" option is has it's right to exist, but what happens if it's turned off? Going back to the example above (Alice is already logged on while the second Alice is authenticating), this would result in two Alice sessions. Imagine a scenario where it's useful to allow certain users to have multiple concurrent sessions but disallow multiple sessions for others: Two administrators could to their job but ordinary users are forced to live in a single session.

It is not possible to achieve these semantics just by leveraging the built-in functionality of Terminal Services. However, you can use Group Policy logon scripts to check if a user is already logged on to another sessions. Here's an example of such a script I've written using Windows PowerShell.

# SingleSession.ps1
# Logon script for users with limited session count.
# Global settings.
# Array of users with limited session count.
$ScriptUsers = "user1", "user2", "user3"
# Maximum number of sessions for each user above.
$MaxSessions = 1
# Logoff executable.
$LogoffCommand = $env:systemroot + "\system32\logoff.exe"
# Trim the user name.
$CurrentUser = $env:username.Trim()
# Welcome message.
Write-Host "Welcome to this server," $CurrentUser
# Cancel if a user that's not contained in $ScriptUsers logs on.
if ($ScriptUsers -inotcontains $CurrentUser)
    Write-Host "You do not need to run this script."
# Get the number of sessions the current user owns.
$WmiUserNameExpression = "*,Name=`"" + $CurrentUser + "`""
$Sessions = get-wmiobject Win32_LoggedOnUser | `
    select Antecedent | `
    where { $_.Antecedent -ilike $WmiUserNameExpression } | `
# Log off the user if $MaxSessions is exceeded.
if ($Sessions.Count -gt $MaxSessions)
    Write-Host "You are already logged on in another session."
    # Show a message box informing the user that he will be logged off.
    $MessageBox = New-Object -ComObject WScript.Shell
    $MessageBox.Popup( `
        "There's another user connected using account " + $CurrentUser + ". Please try to reconnect later.`n`nYour session will be ended now. This window will be closed auomatically after 60 seconds.", `
        # Close message box after 60 seconds.
        60, `
        "Account is being used", `
        # Error Icon.
    # Force logoff.

You just need to set up the logon script using Group Policy.

  1. Enable multiple Terminal Services sessions using the Terminal Services Configuration snap-in (tscc.msc).
  2. Open the Group Policy Editor by running gpedit.msc.
  3. Assign the user logon script.
    Because the PowerShell file extension (ps1) is not linked to the PowerShell runtime, I wrote a little helper cmd file that simply starts the PowerShell script. Enter this cmd file in the Logon Scripts editor.
    rem SingleSession.cmd
    rem Starts SingleSession.ps1.
    @powershell.exe -noprofile -command SingleSession.ps1

Now, at each logon, the script checks if a user contained in $ScriptUsers logs on. If this is the case and the user already owns another session, a message box is displayed saying that he or she will be logged off. After a timeout of 60 seconds the message box closes and the session will be ended.

Basically, I rewrote the SingleSession script that existed as a cmd file for years in PowerShell because I think it's time to switch to the new well thought-out command line. PowerShellIDE, though beta-ish, has done a decent job supporting me during the short development process.

Monad is Dead, Long Live Windows PowerShell

Posted in PowerShell at Friday, 28 April 2006 11:12 W. Europe Daylight Time

Finally, Microsoft has given the new Windows CLI (codenamed Monad) its name: PowerShell. <rant>I can't help myself, but I believe there could be a better name. Why didn't they just stick to Microsoft Shell?</rant>

The product is currently RC1 (download versions for x86, x64 or ia64 and the docs), I will start using it as soon as I get the opportunity of writing a new script.

Update: I found a good reference to the beta name "Monad" in the first chapter of Bruce Payette's book on PowerShell.

“The Monad, of which we shall here speak, is nothing but a simple substance, which enters into compounds. By 'simple' is meant 'without parts.'”
From THE MONADOLOGY by Gottfried Wilhelm Leibniz (translated by Robert Latta)

In the Monadology, Leibniz described a world of irreducible components from which all things could be composed. This excellently captures the spirit of the project – to create a toolkit of simple pieces that you compose to create complex solutions.

Previous Page Page 2 of 2 in the PowerShell category