PowerShell – Log Out v1 – 15 Minutes

email me

I created a log out script for AWS users. If they are idle for at least 60 minutes, a 15 minute countdown window comes up. I added this as a scheduled task to control when the form appears.

 

What the form looks like

 

If the user clicks the button, it closes the form, and does not log off the user.

A few clever bits:

  • I generate the form icon from a base64 string
  • I verify the idle time using a Win32 API
  • I hide the PowerShell window in the scheduled task

 

Code

# MrNetTek
# eddiejackson.net/blog
# 5/19/2020
# free for public use
# free to claim as your own

# BEGIN IDLE CHECK
# Idle Input Win32 API
Add-Type @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PInvoke.Win32 {

    public static class UserInput {

        [DllImport("user32.dll", SetLastError=false)]
        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

        [StructLayout(LayoutKind.Sequential)]
        private struct LASTINPUTINFO {
            public uint cbSize;
            public int dwTime;
        }

        public static DateTime LastInput {
            get {
                DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount);
                DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks);
                return lastInput;
            }
        }

        public static TimeSpan IdleTime {
            get {
                return DateTime.UtcNow.Subtract(LastInput);
            }
        }

        public static int LastInputTicks {
            get {
                LASTINPUTINFO lii = new LASTINPUTINFO();
                lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
                GetLastInputInfo(ref lii);
                return lii.dwTime;
            }
        }
    }
}
'@

# Instantiate object
$Idle = [PInvoke.Win32.UserInput]::IdleTime

# Verify Idle Input
# Must be at least 1 hour to run main function
# Add your own specified hours/minutes here
if ($Idle.Hours -gt 0) {
    # continue to run function
    clear-host    
    write-host "Idle time is at least 1 hour"	
}
else
{
    # main function will not run
    clear-host
    write-host "Idle time is less than 1 hour"
    write-host "Exiting..."   

    $ProcessID = [System.Diagnostics.Process]::GetCurrentProcess()
    $ProcessID = $ProcessID.ID    
    #Add-Type -AssemblyName PresentationFramework
    #[System.Windows.MessageBox]::Show('do not run function')
    & taskkill /PID $ProcessID /t /f
}
# END IDLE CHECK


# LOAD MAIN FUNCTION

function Kill-Session {

    $ErrorActionPreference= 'silentlycontinue'	

	# play alert sound
	$sound = new-Object System.Media.SoundPlayer;
    $sound.SoundLocation="c:\WINDOWS\Media\notify.wav";
    $sound.PlayLooping();
    $flag=$false;

    1..10 | foreach {
        if($_ -gt 1){$flag=$true} else{sleep -s 1}
        if($flag) {$sound.Stop() }
    }


    # load assemblies
	Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
	Add-Type -AssemblyName PresentationFramework
    [System.Windows.Forms.Application]::EnableVisualStyles()   	
	
	# instantiate objects
	$form = New-Object 'System.Windows.Forms.Form'
	$label1 = New-Object System.Windows.Forms.Label
	$label2 = New-Object System.Windows.Forms.Label
	$textbox = New-Object 'System.Windows.Forms.TextBox'
	$buttonStillWorking = New-Object 'System.Windows.Forms.Button'	
	$timer = New-Object 'System.Windows.Forms.Timer'
	$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'	

	$cadence=[timespan]'0:0:0:1'
	$ticker={
		$script:ts1=$ts1.Subtract($cadence)
		$textbox.Text = $ts1.ToString('hh\:mm\:ss')
		if($ts1.Ticks -le 0){
		    $timer.Stop()
		    #force log off	            
		    (Get-WmiObject -Class Win32_OperatingSystem).Win32Shutdown(4) 
		    #[System.Windows.MessageBox]::Show('kill session')
		    $ProcessID = [System.Diagnostics.Process]::GetCurrentProcess()
		    $ProcessID = $ProcessID.ID
		    & taskkill /PID $ProcessID /t /f
		}
	}
	
	$Form_StateCorrection_Load=
	{		
		$form.WindowState = $InitialFormWindowState
	}
	
	$Form_Cleanup_FormClosed=
	{		
		try
		{
			$form.remove_Load($form_Load)
			$timer.remove_Tick($ticker)
			$form.remove_Load($Form_StateCorrection_Load)
			$form.remove_FormClosed($Form_Cleanup_FormClosed)
		}
		catch { Out-Null }
	}

	$form.SuspendLayout()
	
	
	# MAIN FORM	    
	$form.Icon = 'c:\powershell\icon.ico'
	$form.Controls.Add($textbox)
	$form.Controls.Add($buttonStillWorking)
	$form.Controls.Add($label1)
	$form.Controls.Add($label2)    
	$form.AcceptButton = $buttonStillWorking
	$form.AutoScaleDimensions = '8, 17'
	$form.AutoScaleMode = 'Font'
	$form.ClientSize = '616, 362'
	$form.FormBorderStyle = 'FixedDialog'
	$form.Margin = '5, 5, 5, 5'
	$form.MaximizeBox = $False
	$form.MinimizeBox = $False
	$form.Name = 'form1'
	$form.ShortcutsEnabled = $False
	$form.StartPosition = 'CenterScreen'
	$form.Text = 'Log Out v1.0'
	$form.TopMost = $True # force window to stay on top
	$form.add_Load($form_Load)	    
	
	# LABEL1
    $label1.Location = New-Object System.Drawing.Point(100,50)
    $label1.Font = 'Calibri, 13.25pt'
    $label1.Size = New-Object System.Drawing.Size(550,80)
    $label1.Text = 'Your AWS session has been idle for 60 min.'    
    
	
	# LABEL2	    
    $label2.Location = New-Object System.Drawing.Point(195,130)
    $label2.Font = 'Calibri, 13.25pt'
    $label2.Size = New-Object System.Drawing.Size(450,80)
    $label2.Text = 'Logging Session Out In...'    

	
	# TEXTBOX
    $textbox.Font = 'Calibri, 14.25pt, style=Bold'
	$textbox.Location = '264, 180'
	$textbox.Margin = '5, 5, 5, 5'
	$textbox.BorderStyle = "None"
	$textbox.Name = 'textbox'
	$textbox.Size = '100, 34'
	$textbox.ReadOnly = $True
	$textbox.TabIndex = 2	
	
	# BUTTONSTILLWORKING
	$buttonStillWorking.Font = 'Calibri, 12.25pt'
	$buttonStillWorking.DialogResult = 'OK'
	$buttonStillWorking.Location = '210, 265'
	$buttonStillWorking.Margin = '5, 5, 5, 5'
	$buttonStillWorking.Name = 'buttonOK'
	$buttonStillWorking.Size = '200, 50'
	$buttonStillWorking.BackColor ="LightGray"
	$buttonStillWorking.ForeColor ="black"
	$buttonStillWorking.Text = '&I''m still working'
	$buttonStillWorking.UseCompatibleTextRendering = $True
	$buttonStillWorking.UseVisualStyleBackColor = $False
	$buttonStillWorking.TabIndex = 0	
	
	# TIMER
	$timer.Interval = 1000
	$timer.add_Tick($ticker)
	$form.ResumeLayout()
	
	# FORM CONFIG
	$InitialFormWindowState = $form.WindowState
	$form.add_Load($Form_StateCorrection_Load)    
	$form.add_FormClosed($Form_Cleanup_FormClosed) 
    $this.Enabled=$false	
	
	# SET TIMER FOR 15 MINUTES
	$script:ts1 = [timespan]'0:0:15:00'    
	
	# START TIMER	
	$timer.Start()
	
    # FOCUS THIS PROCESS
	$WindowState = '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);'
    add-type -name win -member $WindowState -namespace native
    [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle, 0)    
	SetForegroundWindow(this.Handle)
	
	# SHOW FORM
    $form.ShowDialog()
} 

# LOAD ICON FUNCTION
# Create the form icon
function Generate-Icon {

    # icon stored as base64
	$Base64String = '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'
    
	# icon path and name
	$Image = 'c:\powershell\icon.ico'
    
	# convert base64 to bytes
	[byte[]]$Bytes = [convert]::FromBase64String($Base64String)
    
	# create icon from bytes
	[System.IO.File]::WriteAllBytes($Image,$Bytes)

}

# FORM ICON
# Check if form icon does not exist
if (!(Test-Path c:\powershell\icon.ico)) {
    # if not exist, run icon function
	Generate-Icon
}

# RUN MAIN FUNCTION
Kill-Session

 

Scheduled Task

Code I used to create the scheduled task

$action = New-ScheduledTaskAction -Execute 'mshta.exe' -Argument 'vbscript:Execute("CreateObject(""Wscript.Shell"").Run ""powershell.exe -ExecutionPolicy Bypass -NoLogo -Command """"& C:\powershell\Kill-Session.ps1"""""", 0 : window.close")'
$trigger =  New-ScheduledTaskTrigger -Daily -At 11pm
$idle = New-ScheduledTaskSettingsSet -RunOnlyIfIdle -IdleDuration 01:00:00 -IdleWaitTimeout 0

Register-ScheduledTask -Action $action -Settings $idle -Trigger $trigger -TaskName "Kill-Session" -Description "Log out of AWS Session"

 

 

Notes

if (Get-Process logonui -ComputerName $env:computername -ErrorAction SilentlyContinue)
    {
         Write-Host "machine is locked"
    }
Else 
    {
         Write-Host "machine is not locked"
    }