The script below calculates the shutdown time from my registry as 2019:05:19:00:00 EST
#Definefunction TimeYmdHmsGMT()
Xbias = "System\CurrentControlSet\control\TimeZoneInformation[ActiveTimeBias]"
offset = RegQueryDword(@REGMACHINE,Xbias)
if offset<0 then val=timesubtract(timeymdhms(),strcat("0:0:0:0:",-offset,":0"))
else val=timeadd(timeymdhms(),strcat("0:0:0:0:",offset,":0"))
return val
#endfunction
#DefineFunction TimeAdjustCurrentZone(strUtcTime)
nActiveBias = RegQueryDword(@REGMACHINE,"SYSTEM\CurrentControlSet\Control\TimeZoneInformation[ActiveTimeBias]", 0)
If nActiveBias >= 0
Return TimeSubtract(strUtcTime,"0000:00:00:00:":nActiveBias:":00")
Else
Return TimeAdd(strUtcTime,"0000:00:00:00:":Abs(nActiveBias):":00")
#EndFunction
;Return TimeAdjustCurrentZone(ObjectType("DATE", fDate))
#DefineFunction HexByteToDec(_sHexByte)
_sHexByte = StrUpper(_sHexByte)
nHexOff = 55
nByte = 0
For i = 1 To 2
cNybble = StrSub(_sHexByte,i,1)
If !IsInt(cNybble) Then cNybble = Char2Num(cNybble) - nHexOff
nByte = (nByte << 4) | cNybble
Next
Return nByte
#EndFunction
#DefineFunction Hex2Date(Hex)
bias = "0000:00:00:04:00:00"
aH = Arrayize(Hex," ")
If ArrInfo(aH,1)<8 Then Return ""
aD = ArrDimension(8)
For i = 0 To 7
aD[i] = HexByteToDec(aH[i]) * 1.0
Next
Term = aD[7] * (2.0**56) + aD[6] * (2.0**48) + aD[5] * (2.0**40) + aD[4] * (2.0**32) + aD[3] * (2.0**24) + aD[2] * (2.0**16) + aD[1] * (2.0**8) + aD[0]
Days = Term / (1E7 * 86400)
;fDate = 0.0 + ObjectType("DATE",Days) + ObjectType("DATE","01-01-1601")
fDate = 0.0 + ObjectType("DATE",Days) + ObjectType("DATE","1601:01:01:00:00:00")
;Return(ObjectType("DATE",fDate))
;Return TimeSubtract(ObjectType("DATE",fDate),bias)
Return TimeAdjustCurrentZone(ObjectType("DATE", fDate))
#EndFunction
; Test
RegShutdownTime = RegQueryBin(@REGMACHINE,"System\CurrentControlSet\Control\Windows[ShutdownTime]")
ShutDownTime = Hex2Date(RegShutdownTime)
Message("",ShutDownTime:" EST")
Exit
A similar Powershell script comes up with Monday, May 20, 2019 7:25:59 AM
$regKey = Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows
$shutDown = $regKey.ShutdownTime
$Int64Value = [System.BitConverter]::ToInt64($shutDown, 0)
#$Int64Value
#Get-Date $Int64Value
$date = [DateTime]::FromFileTime($Int64Value)
$date
Outside of the formatting, the PS script has more precision. I will probably try converting the PS into a WB script with the CLR, but would be interested in making the above WB script more precise.
Slightly different approach but it does produce a result identical to your ps script without the use of .Burgerflipper.
#DefineFunction TimeAdjustCurrentZone(strUtcTime)
nActiveBias = RegQueryDword(@REGMACHINE,"SYSTEM\CurrentControlSet\Control\TimeZoneInformation[ActiveTimeBias]", 0)
If nActiveBias >= 0
Return TimeSubtract(strUtcTime,"0000:00:00:00:":nActiveBias:":00")
Else
Return TimeAdd(strUtcTime,"0000:00:00:00:":Abs(nActiveBias):":00")
#EndFunction
RegShutdownTime = RegQueryBin(@REGMACHINE,"System\CurrentControlSet\Control\Windows[ShutdownTime]")
RegShutdownTime = StrReplace(RegShutdownTime, ' ', '')
hFileTime = BinaryAlloc(8)
BinaryPokeHex(hFileTime, 0, RegShutdownTime)
hSysTime = DllStructAlloc('WORD:wYear; WORD:wMonth; WORD:wDayOfWeek; WORD:wDay; WORD:wHour; WORD:wMinute; WORD:wSecond; WORD:wMilliseconds;')
bResult = DllCall('Kernel32.dll',long:'FileTimeToSystemTime', lpBinary:hFileTime, lpstruct:hSysTime)
BinaryFree(hFileTime)
strYMS = StrFixCharsL(DllStructPeek(hSysTime ,'wYear'),'0',4):':'
strYMS := StrFixCharsL(DllStructPeek(hSysTime ,'wMonth'),'0',2):':'
strYMS := StrFixCharsL(DllStructPeek(hSysTime ,'wDay'),'0',2):':'
strYMS := StrFixCharsL(DllStructPeek(hSysTime ,'wHour'),'0',2):':'
strYMS := StrFixCharsL(DllStructPeek(hSysTime ,'wMinute'),'0',2):':'
strYMS := StrFixCharsL(DllStructPeek(hSysTime ,'wSecond'),'0',2) ; Could round the seconds using wmilliseconds value.
DllStructFree(hSysTime)
strYMS = TimeAdjustCurrentZone(strYMS)
strTime = TimeFormat(strYms, "dddd', 'MMMM dd', 'yyyy hh':'mm':'ss t")
Pause('Shutdown Time', strTime)
Neat.... Well on my path to trip over the CLR I tried this:
;Winbatch 2019A - CLR to convert Windows ShutdownTime reg value to date
;
;
;=============================================================================================================
Gosub udfs
ObjectClrOption("useany","System")
RegShutdownTime = RegQueryBin(@REGMACHINE,"System\CurrentControlSet\Control\Windows[ShutdownTime]")
RegShutdownTime = StrReplace(RegShutdownTime, ' ', '')
value = Hex2Dec(RegShutdownTime) ;uses huge math extender
; CLR stuff
oBC = ObjectClrNew( 'System.Convert')
value= oBC.ToInt64(value)
oDT = ObjectClrNew( 'System.DateTime')
;this line gives error - Not a Valid Win32 filetime
dt = oDT.FromFileTime(value)
;final WB function for readable DateTime
dt = TimeFormat(dt, "dddd', 'MMMM dd', 'yyyy hh':'mm':'ss t")
Pause('Shutdown Time', dt)
Exit
:udfs
#DefineFunction Hex2Dec(hex)
AddExtender("wwhug44i.dll") ; Huge Math Extender
str="0123456789ABCDEF"
hex=StrTrim(StrUpper(hex))
hexlen=StrLen(hex)
dec=0
for x=1 to hexlen
by16 = huge_Multiply(dec,16)
ptr = StrIndex(str,strsub(hex,x,1),0,@fwdscan)-1
dec = huge_Add( by16, ptr)
next
return(dec)
#EndFunction
Return
The error is commented in the code. Wondering is this should be run in 64-bit WB.
(Educational thread -- thanks!)
I believe the problem is that the value stored in the registry is little-endian which is how Intel processors represent 64-bit integers in memory. However, MSFT's FCL only understands big-endian representations of 64-bit integers. So you need to do something like the following:
ObjectClrOption("useany","System")
RegShutdownTime = RegQueryBin(@REGMACHINE,"System\CurrentControlSet\Control\Windows[ShutdownTime]")
;; Convert little-endian machine representation of int64 to
;; a big-endian representation.
RegTimeBigEnd = ''
for i=8 to 1 by -1
RegTimeBigEnd := ItemExtract(i, RegShutdownTime, ' ')
next
; CLR stuff
oBC = ObjectClrNew( 'System.Convert')
value= oBC.ToInt64( RegTimeBigEnd, 16)
oDT = ObjectClrNew( 'System.DateTime')
dt = oDT.FromFileTime(System.Int64:value)
;final WB function for readable DateTime
dt = TimeFormat(dt, "dddd', 'MMMM dd', 'yyyy hh':'mm':'ss t")
Pause('Shutdown Time', dt)
Or if you want to use the FCL's registry functions:
objReg = ObjectClrNew( 'Microsoft.Win32.Registry')
objMach = objReg.LocalMachine
objRegWin = objMach.OpenSubKey('System\CurrentControlSet\Control\Windows')
aShutdownTime = objRegWin.GetValue('ShutdownTime')
objRegWin.Close()
objMach.Close()
hShutdownTime = BinaryAlloc(8)
; Converts the byte array to a string.
for i=0 to 7
BinaryPoke(hShutdownTime, i, aShutdownTime[i])
next
strShutdownTime = BinaryPeek8(hShutdownTime, 0)
BinaryFree(hShutdownTime)
; CLR stuff
oBC = ObjectClrNew( 'System.Convert')
value= oBC.ToInt64( strShutdownTime, 10)
oDT = ObjectClrNew( 'System.DateTime')
dt = oDT.FromFileTime(System.Int64:value)
;final WB function for readable DateTime
dt = TimeFormat(dt, "dddd', 'MMMM dd', 'yyyy hh':'mm':'ss t")
Pause('Shutdown Time', dt)
More fun. Another slightly different take:
objReg = ObjectClrNew( 'Microsoft.Win32.Registry')
objMach = objReg.LocalMachine
objRegWin = objMach.OpenSubKey('System\CurrentControlSet\Control\Windows')
aShutdownTime = objRegWin.GetValue('ShutdownTime')
objRegWin.Close()
objMach.Close()
; Converts the byte array to a string.
hShutdownTime = BinaryAlloc(8)
for i=0 to 7
BinaryPoke(hShutdownTime, i, aShutdownTime[i])
next
strShutdownTime = BinaryPeek8(hShutdownTime, 0)
BinaryFree(hShutdownTime)
value = ObjectType('i8',strShutdownTime)
; CLR stuff
oDT = ObjectClrNew( 'System.DateTime')
dt = oDT.FromFileTime(value)
;final WB function for readable DateTime
dt = TimeFormat(dt, "dddd', 'MMMM dd', 'yyyy hh':'mm':'ss t")
Pause('Shutdown Time', dt)
This is educational. After posting my CLR fubar, I started looking into byte-arrays and how they were used for 64bit values. Looking at your code and System.Int64:value [I had tried other combinations with no luck and using .Parse was even more frustrating. Probably need more explanation on when to use the colon, but otherwise my CLR is improving and I thank you sir.
The "System.Int64:value" business was completely unnecessary. In this case, simply using "value" would have worked just fine. I have a habit of including type information in parameters when I first use a method to avoid annoying "method not found" errors when initially writing code snippets. I simply forgot to remove it before posting the example.
In your script using the Windows Registry you processed the Shutdown value as an array. In general is that something you would have to know in advance. I added [ t=aShutdownTime.GetType().Name ] which gave 'Array must have dimensions error'.
[EDIT]: My bad - ObjectTypeGet(aShutdownTime) worked
If you use WinBatch Studio for script creation and development, variable type information is available in the Watch Window every time you run the script to a breakpoint, completion, or error. Doing so makes developing a script with unfamiliar functions and methods much, much quicker.
Quote from: td on June 11, 2019, 07:26:08 AM
If you use WinBatch Studio for script creation and development, variable type information is available in the Watch Window every time you run the script to a breakpoint, completion, or error. Doing so makes developing a script with unfamiliar functions and methods much, much quicker.
Probably beating a dead horse, but prior to closing the registry object I tested Pause(aShutdownTime,objRegWin.GetValueKind('ShutdownTime')) which returned 3 [binary] so happy to note the WB return with ObjectTypeGet() was more informative.
"Binary" is a registry data type but not a programming language data type. Obviously, the two types are not synonymous.