How to run a macro just before Windows shuts down - windows

I would like to run a macro just before Windows shutsdown to log the time.
I wrote below program to run the macro just right after windows starts up. I will put the shortcut in Windows startup folder so that when windows starts, it will log the current day time and date.
But how to run a macro just before shutting down the PC.
The same below code will work for Start up and shut down with little change.
But how to run the macro or code before windows turns off
Private Sub Workbook_Open()
'
' Macro1 Macro
'
Dim Day As Date
Dim Tim As Date
Dim Row As Integer
Dim Col As Integer
Row = 1
Col = 1
Day = DateValue(Now)
Tim = TimeValue(Now)
While (Cells(Row, Col) <> "")
Row = Row + 1
Wend
Cells(Row, Col) = Day
Cells(Row, Col + 1) = Tim
ThisWorkbook.Save
'
End Sub

If you put something in the startup folder, it will not really run at startup, but at user logon.
To run something at startup or shutdown, you need to implement startup or shutdown scripts through Group Policy. Type "gpedit.msc" in the run box to explore Group Policy.
Another solution is the system eventlog, events from source "eventlog" are logging all startup and shutdown activity.

Related

SendKeys doesn't work from background task

Initial Problem:
I use an external keyboard at the office, so I want the NumLock ON. But when I'm at home I just use the laptop keyboard, so then I get numbers instead of letters and I have to turn NumLock OFF.
Initial Solution:
The below script detects one or two keyboards and turns NumLock ON or OFF as appropriate.
New Problem:
This works perfectly from the command line, but I want it to trigger when I log in and happen automatically. When I run it from Task Scheduler in the background, this line doesn't work:
Shell.SendKeys "{NUMLOCK}"
It fires but doesn't toggle the lock. No errors reported.
UPDATE: If I schedule it to run under my account "only when user is logged on" then it works but shows the cmd window. If I run it under my account or under the SYSTEM account with "whether user is logged in or not" the window goes away nicely but it doesn't work.
Whether from cmd or run as a scheduled task, I get this output when it should toggle the lock:
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.
Found HID Keyboard Device
Found HID Keyboard Device
numLock is OFF
Toggling Numlock
So the script itself is working correctly.
UPDATE2: Looks like it might have something to do with not having a windows station when running as a background task. Turns out that DetectNumlockConsole.exe isn't working either. That is a simple c# app that returns the results of this line
numLock = Control.IsKeyLocked(Keys.NumLock);
Again, this works when run "only when user is logged on" but not "whether user is logged in or not."
--------- vbs script -----------
set OUT = WScript.StdOut
Set Shell=CreateObject("Wscript.Shell")
Dim KeyCount
KeyCount = 0
Computer = "."
'set NumLock = CheckState
Set WMIService = GetObject("winmgmts:\\" & Computer & "\root\cimv2")
Set Devices = WMIService.ExecQuery ("Select * From Win32_USBControllerDevice")
For Each Device in Devices
DeviceName = Device.Dependent
Quotes = Chr(34)
DeviceName = Replace(DeviceName, Quotes, "")
DeviceNames = Split(DeviceName, "=")
DeviceName = DeviceNames(1)
Set USBDevices = WMIService.ExecQuery ("Select * From Win32_PnPEntity Where DeviceID = '" & DeviceName & "'")
For Each USBDevice in USBDevices
'OUT.WriteLine USBDevice.Description ' Write description to command line to see what to look for
If InStr( LCase( USBDevice.Description ), "keyboard" ) <> 0 Then
KeyCount = KeyCount + 1
OUT.WriteLine "Found " & USBDevice.Description
End If
Next
Next
dim numLock
numLock = Shell.Run("DetectNumlockConsole.exe",0,True)
If (numLock = 0) Then
OUT.WriteLine "numLock is OFF"
Else
OUT.WriteLine "numLock is ON"
End If
' If we have a keyboard, and numlock is OFF
' Or we don't have a keyboard, and numlock is ON
' Then toggle it
If (((KeyCount > 1) AND (numLock = 0)) OR ((KeyCount = 1) AND (numLock = 1))) Then
Shell.SendKeys "{NUMLOCK}" ' *** Problem here, doesn't toggle **
OUT.WriteLine "Toggling Numlock"
End If
That is how windows security works. It is nothing to do with sendkeys per se, but tasks under different security contexts cannot affect other tasks.
As you can see it works when run under the same security context as only run when user logged in does.
It's called process isolation and the principal is that no one can mess with the interactive user for both security and UI principals.

Output Uptime to file and acumulate

Hey guys im im trying to get a cumulative uptime data of my network machines easily.
Essentially we need the total of the uptime saved to a file so at say the end of the quarter we can report on this.
I can easily get uptime at the time i run a script, but unsure how to go about one that saves the uptime of the machine each day so i can then report back on it.
Any help is appreciated.
If you want to do this with the uptime that the machine itself monitors you need to (more or less) continuously update your log, because the counter is reset after a system crash or reboot you'll lose the uptime information between the last log write and the crash/reboot. A better approach would be to implement a system monitoring solution (Nagios, Zabbix, WhatsUp, SCOM, ...). These tools continuously track the system uptime and already provide reports that you can use.
If you still want to go the "local script" route, running something like the following every 5 minutes as a scheduled task might work:
Set fso = CreateObject("Scripting.FileSystemObject")
Set wmi = GetObject("winmgmts://./root/cimv2")
Set re = New RegExp
logfile = "C:\path\to\uptime.log"
uptimes = fso.OpenTextFile(logfile, 1, True).ReadAll
'extract last uptime from log
re.Pattern = "(\d+)\s*$"
For m In re.Execute(uptimes)
lastUptime = CLng(m.SubMatches(0))
Next
'get current uptime
qry = "SELECT * FROM Win32_PerfFormattedData_PerfOS_System"
For Each v In wmi.ExecQuery(qry)
currentUptime = CLng(v.SystemUpTime)
Next
Set f = fso.OpenTextFile(logfile, 2)
If IsEmpty(lastUptime) Or lastUptime >= currentUptime Then
'append current uptime if the last uptime was greater or equal to than the
'current uptime (=> a crash or reboot occurred) or no uptime was logged before
f.WriteLine uptimes & currentUptime
Else
'update last uptime otherwise
f.WriteLine re.Replace(uptimes, currentUptime)
End If
f.Close

Identify CD drive and eject using bat or vbs while in WinPE without external files

I need to identify the CD drive and eject the tray. This is executed while booted in WinPE, so the WMP eject function is not available. This script will be used on various computer models/configurations. I'm currently using this:
For Each d in CreateObject("Scripting.FileSystemObject").Drives
CreateObject("Shell.Application").Namespace(17).ParseName("D:\").InvokeVerb("Eject")
Next
It works, but sometimes it errors and requires user interaction before it ejects. I suspect that it's because of the hardcoded D:\ drive letter, but I could be completely wrong. I need this to work without 3rd party utilities.
Use the DriveType property of the Drive object:
For Each d in CreateObject("Scripting.FileSystemObject").Drives
WScript.sleep 60
If d.DriveType = 4 Then
CreateObject("Shell.Application").Namespace(17).ParseName(d.DriveLetter & ":\").InvokeVerb("Eject")
End If
Next
Here is code that uses Media Player to eject; I'm not sure how easy it would be to invoke from your WinPE environment:
' http://www.msfn.org/board/topic/45418-vbscript-for-openingclosing-cd/
' http://waxy.org/2003/03/open_cdrom_driv/
Set oWMP = CreateObject("WMPlayer.OCX.7" )
Set colCDROMs = oWMP.cdromCollection
For d = 0 to colCDROMs.Count - 1
colCDROMs.Item(d).Eject
Next 'null
Plan B would be to download a copy of "eject.exe", and include it on your WinPE media:
http://www.911cd.net/forums/index.php?showtopic=2931&hl=cd+eject

VBS Windows 64bit/32bit registry read problem

What I am trying to achieve I feel should be fairly simple yet it is driving me totally insane.
Background:
We run a system monitoring tools across our clients which has the ability to run .vbs scripts remotely. This works very well normally.
What I am trying to achieve currently is being able to read a line from the registry on both 32bit versions of windows and 64 bit versions.
The Client side.exe which monitors the machine runs as a 32bit process on BOTH platforms (This is the trick).
I want to read a key from HKEY_LOCAL_MACHINE\SOFTWARE\ for example. My script works perfectly fine on 32bit. example: objRegistry.RegRead("HKEY_LOCAL_MACHINE\Software\anything")
The problem I have is when I run this same line on a 64bit folder is is automatically looking in the wow64node folder. Example: objRegistry.RegRead("HKEY_LOCAL_MACHINE\Software\wow64node\").
I need to have it checking in EXACTLY the same place.
The key it is reading is part of a program that runs both 32bit and 64bit versions which is why it is not installed in the wow64node folder.
At this point I am unable to run the .VBS script as a 64bit process which would solve my problem entirely as it would then not look in the wow64node folder.
If anyone has any ideas at all please let me know.
I solved it using this piece of code.
Const HKEY_LOCAL_MACHINE = &H80000002
sPath = ReadRegStr (HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\ASP.NET\2.0.50727.0", "Path", 64)
WScript.Echo sPath
' Reads a REG_SZ value from the local computer's registry using WMI.
' Parameters:
' RootKey - The registry hive (see http://msdn.microsoft.com/en-us/library/aa390788(VS.85).aspx for a list of possible values).
' Key - The key that contains the desired value.
' Value - The value that you want to get.
' RegType - The registry bitness: 32 or 64.
'
Function ReadRegStr (RootKey, Key, Value, RegType)
Dim oCtx, oLocator, oReg, oInParams, oOutParams
Set oCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
oCtx.Add "__ProviderArchitecture", RegType
Set oLocator = CreateObject("Wbemscripting.SWbemLocator")
Set oReg = oLocator.ConnectServer("", "root\default", "", "", , , , oCtx).Get("StdRegProv")
Set oInParams = oReg.Methods_("GetStringValue").InParameters
oInParams.hDefKey = RootKey
oInParams.sSubKeyName = Key
oInParams.sValueName = Value
Set oOutParams = oReg.ExecMethod_("GetStringValue", oInParams, , oCtx)
ReadRegStr = oOutParams.sValue
End Function
Thank you Helen for your help!
Instead of WshShell.RegRead, use the WMI StdRegProv class — it allows you to specify whether you want to read from the 32-bit or 64-bit registry. Check out this MSDN article for more info and examples:
Requesting WMI Data on a 64-bit Platform

Find out if computer rebooted since the last time my program ran?

How can my program know if windows rebooted since the last time it ran? All versions of windows XP and on.
This can be accomplished trivially using the global atom table. Just make sure your atom name is unlikely to conflict with another atom.
if (GlobalFindAtom ("MySecretName") == 0)
{
// First time run since reboot
GlobalAddAtom ("MySecretName");
}
There's a Windows API call you can make called GetTickCount...
http://msdn.microsoft.com/en-us/library/ms724408%28VS.85%29.aspx
Edit: The idea is that when your program starts, you make a call to GetTickCount (which returns how many milliseconds Windows has been running), and then calculate an exact start date (right now minus the number of milliseconds). Store that date, and then the next time your program starts, calculate the date again and compare it to the previously stored date. If the dates are different, Windows has rebooted. Use GetTickCount64 if possible (but don't code your solution solely using this function.
You can use WMI:
strComputer = "."
Set objWMIService = GetObject _
("winmgmts:\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
("Select * from Win32_OperatingSystem")
For Each objOS in colOperatingSystems
dtmBootup = objOS.LastBootUpTime
dtmLastBootupTime = WMIDateStringToDate(dtmBootup)
dtmSystemUptime = DateDiff("h", dtmLastBootUpTime, Now)
Wscript.Echo dtmSystemUptime
Next
Function WMIDateStringToDate(dtmBootup)
WMIDateStringToDate = CDate(Mid(dtmBootup, 5, 2) & "/" & _
Mid(dtmBootup, 7, 2) & "/" & Left(dtmBootup, 4) _
& " " & Mid (dtmBootup, 9, 2) & ":" & _
Mid(dtmBootup, 11, 2) & ":" & Mid(dtmBootup, _
13, 2))
End Function
net statistics workstation|find "Statistics since"
The Microsoft utility uptime.exe "processes the machine's event log to determine system availability and current uptime".
Simple, but ugly solution : just launch a never-ending dummy process :-)
If it's still here, you didn't reboot. If it's not, chances are that you have just rebooted.
In the vein of ugly hacks ... stick something in one of the RunOnce registry keys
How about adding a file to %TMP% and check if it's still there (%TMP% should be cleared at each reboot by Windows)
or
more robust way, create a file somewhere and mark it for deletion on next reboot (see MoveFileEx API) and check that file

Resources