Bookmark and Share

(Note: These solutions were written for Advanced  Event 9 of the 2010 Scripting Games.)

 

Advanced Event 9 (Windows PowerShell)

Photo of Niklas Goude

Niklas Goude is an author, IT consultant, and trainer at Enfo Zipper in Stockholm, Sweden. Niklas has extensive experience in automating and implementing SharePoint environments using Windows PowerShell. Niklas has written a Windows PowerShell book for Swedish IT pros, and is currently co-authoring a book with Mattias Karlsson titled, PowerShell for Microsoft SharePoint 2010 Administrators, which will be published in English by McGraw-Hill later this year. Niklas runs the blog, http://www.powershell.nu, where he shares scripts, examples, and solutions for administrative tasks in Windows environments through Windows PowerShell.

The hows

The script is written in a pretty straightforward way. There’s a param at the top followed by a help function, followed by script functions, and main code at the bottom of the script. The true “magic” achieved in the script lies in the do…while loops that check if a process is running. The niceMode switch relies on the same technique. There’s also a switch included that checks which type of operating system the computer is running. Let’s walk through the do…while loops that achieve the “magic.” 

do {

    if(Get-Process | Where { $_.Path -match "\\$program$" -OR $_.ProcessName -eq "$program" }) { $programStarted = $true }

  } while ($programStarted -ne $true) 

The first do…while loop checks if a given program (executable file or process name ) is running on the computer, and, if not, waits until the program is started. The loop continues as long as the given program is not started. When the program is started on the computer, the script continues:

  if($niceMode) {

    do {

      if(Get-Process | Where { $_.ProcessName -eq "WINWORD" -OR $_.ProcessName -eq "EXCEL" }){

        "Excel or Word"

        Start-Sleep -Seconds 300

      } else {

        $ExcelOrWord = $true

      }

    } while ($ExcelOrWord -ne $true)

  }

The second do…while loop checks if niceMode is active, and if niceMode is active, the script checks if Microsoft Word or Excel is running on the computer. If either of the Office programs is running, the script waits for five minutes and checks again to see if either Word or Excel is running on the computer. As soon as they are not running, the script continues.

  switch((get-wmiobject Win32_OperatingSystem).BuildNumber) {

    { $_ -eq 2600 }{ funXP }

    { $_ -eq 6000 } { funVista }

    { $_ -eq 7600 } { funWin7 }

    Default { funOther }

  } 

The script then checks which type of operating system that is installed on the computer. The script supports three operating systems: Windows 7, Windows Vista, and Windows XP. All other operating systems are classified as “other.” Depending on the operating system, different functions are executed. The functions display different graphics for the user: Windows 7 uses the Windows Presentation Foundation, Windows Vista displays a form, and Windows XP uses Merlin. Other operating systems use the Write-Progress cmdlet to display the progress.

Here are four screen shots displaying the different operating systems:

 

 

 


After the graphics are displayed, the current logged-on user is logged off through WMI:

(gwmi win32_operatingsystem).Win32Shutdown(4) | Out-Null

The number 4 is used to force a logoff. The command is then piped to the Out-Null cmdlet.

The whys

The script’s main focus is on the different ways of presenting the graphics. The script includes the Microsoft Agent, Windows Forms, Windows Presentation Foundation, and the Write-Progress cmdlet included in Windows PowerShell 2.0. I think that combining different technologies such as WMI, COM, and the .NET Framework in a script shows the benefits of using Windows PowerShell to automate your environments.

The special stuff

The functions included in the script show the different approaches you can take to handle different operating systems through Windows PowerShell. The Microsoft Agent doesn’t work as well on Windows 7 or Windows Vista as it does on Windows XP and the Windows Presentation Foundation is better suited for newer operating systems. You can handle this in a simple and fun way using Windows PowerShell.

The full script is shown here:

param([string]$program, [switch]$niceMode, [switch]$help) 

function funhelp { 

  #################################################################

  #

  # A Help function explaining how to use the script

  #

  #################################################################

$HelpText = @" 

DESCRIPTION:

NAME: AEvent9 

Checks if a program is started and logs off current user 

PARAMETERS:

-program    program executable or processname

-niceMode   Checks if Microsoft Word or Excel is active and waits until

            Microsoft Word or Excel is shut down. A check is done

            every 5 minutes.

-help       diplays the help text.        

SYNTAX: 

.\AEvent9.ps1 -program calc.exe 

Waits until calc.exe is started. When calc.exe is started, the current user will be prompted with a countdown and logged off within 60 seconds.

.\AEvent9.ps1 -program calc -niceMode 

Waits until calc.exe is started. When calc.exe is started, the script checks if Microsoft Word or Excel is active and waits until the programs are shut down.

The user will then be prompted with a countdown and logged off within 60 seconds. 

.\AEvent9.ps1 -program calc -help 

Displays the help topic for the script 

"@

$HelpText 

} 

function funXP { 

  #################################################################

  #

  # Rely on Merlin to do the job

  #

  #################################################################

  $GetAgent = new-object -com Agent.Control.2

  $GetAgent.Connected = 1

  [void]$GetAgent.Characters.Load("Merlin")

  $Agent = $GetAgent.Characters.Character("Merlin")

  [void]$Agent.Show() 

  for($CountDown = 60; $CountDown -ge 0; $CountDown--) {

    $speak = $Agent.Speak("Logoff in: $CountDown seconds")

    do {

      #Wait for Speak.status to equal 1

    } while ($speak.status -ne 1)

  }

} 

function funVista { 

  #################################################################

  #

  # Using WinForms to present the outcome

  #

  #################################################################

  Add-Type –assemblyName System.Windows.Forms

  $Form = New-Object Windows.Forms.Form

  $Form.Text = "Logoff Computer"

  $RichTextBox = New-Object System.Windows.Forms.RichTextBox

  $RichTextBox.Dock = 'Fill'

  $RichTextBox.AppendText("Logoff in: 60 seconds")

  # Skapa ett TimerJob

  $CountDown = 60

  $Timer = New-Object System.Windows.Forms.Timer

  $Timer.Interval = 1000

  $Timer.Add_Tick({

    $CountDown--

    if($CountDown -eq -1) {

      $Timer.Stop()

      $Form.Close()

    } else {

      $RichTextBox.AppendText("`n")

      $RichTextBox.AppendText("Logoff in: $CountDown seconds")

      $RichTextBox.Update()

    }

  })

  $Timer.Enabled = $True

  $Timer.Start()

  $Form.Controls.addRange($RichTextBox)

  $Form.Add_Shown({$Form.Activate()})

  [void]$Form.Showdialog()

}

function funWin7 {

  #################################################################

  #

  # Power of Win 7 and Presentation Foundation

  # Start Windows PowerShell using a using a single-threaded apartment

  #

  #################################################################

  powershell.exe -Sta -Command {

    # Add .NET Classes to Session

    Add-Type –assemblyName PresentationFramework

    Add-Type –assemblyName PresentationCore

    Add-Type –assemblyName WindowsBase

    $window = New-Object Windows.Window

    $window.Title = $window.Content = "Logoff in: 60 seconds"

    $window.AllowsTransparency = $True

    $window.WindowStyle = "None"

    $Window.Background="Transparent"

    $window.SizeToContent = “WidthAndHeight”

    $window.FontSize = 100

    $Window.FontFamily="Calibri"

    $Window.FontWeight= "800"

    $window.WindowStartupLocation= "CenterScreen"

    $updateBlock = {

      $CountDown--

      if($CountDown -eq -1) {

        $Timer.Stop()

        $Window.Close()

      } else {

        $window.Title = $window.Content = "Logoff in: $CountDown seconds"

      }

    }

    $Window.Add_SourceInitialized({

      $CountDown = 60

      $Timer = new-object System.Windows.Threading.DispatcherTimer

      $Timer.Interval = [TimeSpan]"0:0:1.00"

      $Timer.Add_Tick( $updateBlock )

      $Timer.Start()

    })

    [void]$window.ShowDialog()

  }

}

function funOther {

  #################################################################

  #

  # Using the Write-Progress cmdlet

  #

  #################################################################

  for($CountDown = 60; $CountDown -ge 0; $CountDown–-) {

    Write-Progress -Activity “Countdown” -SecondsRemaining $CountDown -Status "Logoff in:"

    Start-Sleep -Seconds 1

  }

}

if($help) {

  funhelp

} else {

  #################################################################

  #

  # Wait for program to start, checking both .exe file and processname

  #

  #################################################################

  do {

    if(Get-Process | Where { $_.Path -match "\\$program$" -OR $_.ProcessName -eq "$program" }) { $programStarted = $true }

  } while ($programStarted -ne $true)

  #################################################################

  #

  # if niceMode, check for Excel or Word and wait for Exit

  # check every 300 seconds ( 5 minutes )

  #

  #################################################################

  if($niceMode) {

    do {

      if(Get-Process | Where { $_.ProcessName -eq "WINWORD" -OR $_.ProcessName -eq "EXCEL" }) {

        Start-Sleep -Seconds 300

      } else {

        $ExcelOrWord = $true

      }

    } while ($ExcelOrWord -ne $true)

  }

  #################################################################

  #

  # Check which function to run depending on OS BuildNumber

  #

  #################################################################

  switch((get-wmiobject Win32_OperatingSystem).BuildNumber) {

    { $_ -eq 2600 }{ funXP }

    { $_ -eq 6000 } { funVista }

    { $_ -eq 7600 } { funWin7 }

    Default { funOther }

  }

  #################################################################

  #

  # Force logOff using numeric 4 ( Force logoff )

  #

  #################################################################

  (gwmi win32_operatingsystem).Win32Shutdown(4) | Out-Null

}

 

 

Advanced Event 9 (VBScript)

Photo of Salvador Manaois III

Salvador Manaois III is a senior systems engineer at Infineon Technologies Asia Pacific Pte Ltd. He currently holds the MCITP certification for both Enterprise Administrator and Server Administrator. He actively evangelizes the use of automation (through scripts and other technologies) both at work and in the various IT user groups in which he is involved. He is also a moderator for The Official Scripting Guys Forum and maintains the Bytes & Badz and Scripting, the SysAdmin Way blogs.

-----------

The challenge calls for logging off the user if a certain executable is running (configurable as an argument passed via the command-line interface).

The script checks for the argument and exits (after displaying the syntax for the script) if the argument is null:

If (Wscript.Arguments.Count = 0) or (Wscript.Arguments.Count > 1) Then

  Wscript.Echo "Syntax: cscript logmeout.vbs file1"

  Wscript.Echo vbCrLf

  Wscript.Echo vbTab & "where file1 is the file name of the executable being tracked."

  Wscript.Echo vbCrLf

  Wscript.Quit

End If


If a valid argument is found, the script proceeds to check if either Microsoft Word or Microsoft Excel is running; if either of these is active, the script will pause for five (5) minutes and check again if any of the two applications are running. The Win32_Process class is queried for the executables of Microsoft Word and Microsoft Excel, which are initially defined in the array arrApps. A variable, IsRunning, is used to call the delay (if either of the processes in arrApps is running).

strComputer = "."

arrApps = array("EXCEL.EXE","WINWORD.EXE")

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

IsRunning = 1

Do While IsRunning = 1

  Set appProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" _

  & arrApps(0) & "' or Name = '" & arrApps(1) & "'")

  IsRunning = 0

  For Each objProcess in appProcessList

    IsRunning = 1

  Next

  If IsRunning = 1 then Wscript.Sleep 300000

Loop

If neither Microsoft Word nor Microsoft Excel is running, the script will call two procedures: ShowProgress and LogOff. Again, the Win32_Process class is queried for the process executable passed as the command-line argument.

The ShowProgress procedure uses Internet Explorer to graphically inform the user of the logoff initiated. It prompts the user to save his work. A countdown (60 seconds) is shown to the user and when this reaches 0, the LogOff procedure is invoked. The countdown is decremented by 1 second between 60 seconds to 1 seconds

Image of logoff initiated and counting down

 

Sub ShowProgress

  On Error Resume Next

  Set colItems = objWMIService.ExecQuery("Select * From Win32_DesktopMonitor")

  For Each objItem in colItems

      intHorizontal = objItem.ScreenWidth

      intVertical = objItem.ScreenHeight

  Next 

  Set objExplorer = CreateObject("InternetExplorer.Application")

  objExplorer.Navigate "about:blank"

  objExplorer.ToolBar = 0

  objExplorer.StatusBar = 0

  objExplorer.Left = (intHorizontal - 400) / 2

  objExplorer.Top = (intVertical - 200) / 2

  objExplorer.Width = 400

  objExplorer.Height = 200

  objExplorer.Visible = 1

  objExplorer.Document.Body.Style.Cursor = "wait"

  objExplorer.Document.Title = "LogMeOff v.01"

  For i = 60 to 1 Step - 1

    objExplorer.Document.Body.InnerHTML = "You are being logged off from this PC " _

        & "in " & i & " seconds. Please save your work now."

    Wscript.Sleep 2000

  Next

  objExplorer.Document.Body.InnerHTML = "Logging off..."

  objExplorer.Document.Body.Style.Cursor = "default"

  Wscript.Sleep 2000

  objExplorer.Quit

End Sub


The LogOff uses the Win32Shutdown method of the Win32_OperatingSystem class. Specifically, the Forced Logoff value (4) is used to log off the user from the computer.

Sub LogOff

  Set colItems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")

  For Each objItem in colItems

      objItem.Win32Shutdown(4)

  Next

End Sub

 

The complete script is shown here:

If (Wscript.Arguments.Count = 0) or (Wscript.Arguments.Count > 1) Then

  Wscript.Echo "Syntax: cscript logmeout.vbs file1"

  Wscript.Echo vbCrLf

  Wscript.Echo vbTab & "where file1 is the file name of the executable being tracked."

  Wscript.Echo vbCrLf

  Wscript.Quit

End If

strComputer = "."

arrApps = array("EXCEL.EXE","WINWORD.EXE")

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

IsRunning = 1

Do While IsRunning = 1

  Set appProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" _

  & arrApps(0) & "' or Name = '" & arrApps(1) & "'")

  IsRunning = 0

  For Each objProcess in appProcessList

    IsRunning = 1

  Next

  If IsRunning = 1 then Wscript.Sleep 300000

Loop

Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" _

  & Wscript.Arguments(0) & "'")

      For Each objProcess in colProcessList

        If UCase(Wscript.arguments(0)) = UCase(objProcess.Caption) then

           ShowProgress

           LogOff

        End If

      Next

Sub LogOff

  Set colItems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")

  For Each objItem in colItems

      objItem.Win32Shutdown(4)

  Next

End Sub

Sub ShowProgress

  On Error Resume Next

  Set colItems = objWMIService.ExecQuery("Select * From Win32_DesktopMonitor")

  For Each objItem in colItems

      intHorizontal = objItem.ScreenWidth

      intVertical = objItem.ScreenHeight

  Next

  Set objExplorer = CreateObject("InternetExplorer.Application")

  objExplorer.Navigate "about:blank"

  objExplorer.ToolBar = 0

  objExplorer.StatusBar = 0

  objExplorer.Left = (intHorizontal - 400) / 2

  objExplorer.Top = (intVertical - 200) / 2

  objExplorer.Width = 400

  objExplorer.Height = 200

  objExplorer.Visible = 1

  objExplorer.Document.Body.Style.Cursor = "wait"

  objExplorer.Document.Title = "LogMeOff v.01"

  For i = 60 to 1 Step - 1

    objExplorer.Document.Body.InnerHTML = "You are being logged off from this PC " _

        & "in " & i & " seconds. Please save your work now."

    Wscript.Sleep 1000

  Next

  objExplorer.Document.Body.InnerHTML = "Logging off..."

  objExplorer.Document.Body.Style.Cursor = "default"

  Wscript.Sleep 2000

  objExplorer.Quit

End Sub

 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys