jantari / LSUClient

Orchestrate driver, BIOS/UEFI and firmware updates for Lenovo computers 👨‍💻
https://jantari.github.io/LSUClient-docs/
MIT License
202 stars 21 forks source link

Drivers that are installed being shown as not installed because of DriverDate comparison being thrown off by local timezone #33

Closed osxdude closed 3 years ago

osxdude commented 3 years ago

Testing this out on a few laptops, but I'm looking mainly at my T590 (20N4) on Win10 20H2.

I've noticed when I run Get-LSUpdate at a PowerShell prompt, eventually the window disappears and there isn't any indication that it's running anymore. This is especially annoying using $updates as the variable clears once the window closes. I think it does complete the Get-LSUpdate process. After checking -Debug, it looks like it during _ExternalDetection of tbtesdkfw_1015 is when it closes. If I add an Export-Csv to the command, I do get a list of 18 updates of which most are installed according to Lenovo's tools.

jantari commented 3 years ago

If I understand it correctly you are experiencing two seperate issues?

  1. The powershell window dissappearing during Get-LSUpdate
  2. Updates that are already installed according to Lenovos' tools showing up as needed/not-installed in LSUClient

For issue 1 I was able to reproduce it.

It's a very annoying problem, because it's apparently just the intended behavior of this Lenovo tool, which is being run as part of the detection process of this package, to hide the console it was started from. If you download this exe manually and run it from a cmd or PowerShell window, it'll hide that window too. No idea why they're doing that. You are correct in that this doesn't stop the commands from finishing, the PowerShell process continues to run normally in the background.

I have found a few ways I could prevent this:

I'm going to have to spend some time trying out things and testing the Runspace idea, one (kind of silly) thing you could do in the meantime is use the following code to un-hide the window again after it's gone missing:

Add-Type -TypeDefinition ' using System; using System.Runtime.InteropServices; public class NativeMethods { [DllImport("Kernel32.dll", SetLastError=true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll", SetLastError=true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("User32.dll", SetLastError=true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ShowWindow([In] IntPtr hWnd, [In] Int32 nCmdShow); public static int RestoreConsoleVisibility() { IntPtr hWndConsole = GetConsoleWindow(); bool IsVisible = IsWindowVisible(hWndConsole); Console.WriteLine("Console currently visible? : {0}", IsVisible); if (hWndConsole == IntPtr.Zero) { Console.WriteLine("GetConsoleWindow returned NULL-pointer"); return Marshal.GetLastWin32Error(); } else { Console.WriteLine("GetConsoleWindow returned a valid pointer ({0}), restoring window", hWndConsole); if (ShowWindow(hWndConsole, 5) == true) { // the window was previously visible Console.WriteLine("The console window was already visible"); return 0; } else { // the window was previously hidden Console.WriteLine("Restored the console window after it was hidden"); return 0; } } } } ' Get-LSUpdate <# your parameters #>; [NativeMethods]::RestoreConsoleVisibility()

This uses the ShowWindow() API to restore the hidden window to a visible state after Get-LSUpdate has finished.

For issue 2, could you provide the exact package information that LSUClient is returning (e.g. Get-LSUpdate -All | Format-Table ID, IsApplicable, IsInstalled) and what exact packages the official Lenovo tools offer you so I can compare it? Ideal would be if you could also share the -Verbose and -Debug output log from Get-LSUpdate.

Thank you!

osxdude commented 3 years ago

Yeah, sorry about asking two questions; I realize that is frowned upon and noticed it as I was running tests. And thank you for the workaround for the hidden window. If you want me to open another issue, let me know. But, for now...

Here is the debug output and GitHub doesn't like the CSV that PS outputs, so I'll put it in a code block. Stay tuned for the Lenovo Vantage update history.

ID | IsApplicable | IsInstalled
nzxms01w | FALSE | FALSE
n20p414w | FALSE | FALSE
n20p913w | FALSE | FALSE
n2xtt01w | FALSE | FALSE
n2ic202w | FALSE | TRUE
n2ioi08w | TRUE | TRUE
n2isr04w | FALSE | FALSE
n2irx01w | FALSE | TRUE
n2irw08w | TRUE | FALSE
n20pb10w | FALSE | FALSE
suutspatchww01 | FALSE | FALSE
n20p812w | FALSE | FALSE
n2idg12w | FALSE | FALSE
n2ihd07w | TRUE | FALSE
n2ita09w | TRUE | FALSE
n2ith04w | FALSE | FALSE
tbtesdkfw_1015 | FALSE | FALSE
n2id210w | TRUE | TRUE
n20p510w | FALSE | FALSE
n2iib11w | TRUE | FALSE
lenovoprovisiondolbyvisionp02 | FALSE | FALSE
n2igr07w_gen | FALSE | FALSE
n2igr07w_rtk | TRUE | FALSE
n2icu01w_6sf56a6 | FALSE | FALSE
n2icu01w_7sf102n2 | FALSE | FALSE
n2icu01w_9sf126n3 | FALSE | FALSE
n2icu01w_am-9sf5415 | FALSE | FALSE
n2icu01w_bison | FALSE | FALSE
n2icu01w_bnfh1nksj | FALSE | FALSE
n2icu01w_cnfhh14 | FALSE | FALSE
n2icu01w_cnfjh72 | FALSE | FALSE
ar_tbtdockfw08 | FALSE | FALSE
n2iwa04w | FALSE | FALSE
n2iil10w | FALSE | FALSE
n2ist11w | TRUE | FALSE
nz6gf12w | TRUE | FALSE
n2iwa05w | FALSE | FALSE
n2ia308w | TRUE | FALSE
nz4wi04w | FALSE | FALSE
nz4wh02w | FALSE | FALSE
n2icm17w_rtk | FALSE | FALSE
n2icm17w_son | FALSE | FALSE
n2icm17w_sun | TRUE | FALSE
hlud13ww_rs | FALSE | FALSE
tvsu_trdockfw_31682 | FALSE | FALSE
fwsa14 | FALSE | FALSE
nz4wj10w | FALSE | FALSE
n20pe07w | TRUE | FALSE
n20pc06w | FALSE | FALSE
n2imd13w | TRUE | FALSE
n2iuj11w | FALSE | TRUE
n2iuj28w_n2i | FALSE | TRUE
fwnva43 | FALSE | FALSE
n1zdy58w | TRUE | FALSE
n2hku67w_x64 | TRUE | FALSE
n2iiw15w | TRUE | FALSE
nz3gs09w | TRUE | FALSE
fhybd18 | FALSE | FALSE
fusbcd07 | FALSE | FALSE
wldcgt09 | FALSE | FALSE
n2jrg30w | TRUE | FALSE
n1fup91w | TRUE | FALSE
r0yvu34w | TRUE | FALSE

Here's the Lenovo Vantage update history. Let me know whatchya think.

jantari commented 3 years ago

Hmm... basically all of those drivers LSUClient is falsely showing you are being detected as not installed yet because the currently installed driver is exactly one day older than expected.

That makes me think it may be a timezone issue, like I may be comparing dates from different timezones and that's why they end up one day apart.

Could you run the following two commands and give me the output, that will help me determine whether it's a timezone issue:

[datetime]::UtcNow - [datetime]::Now

and

Get-PnpDevice |
    Where-Object { $_.HardwareId -like "PCI\VEN_8086&DEV_15BF*" } |
    Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_DriverDate' |
    Select-Object -ExpandProperty Data |
    Format-List

Thanks!

osxdude commented 3 years ago

UTC - Local Time =

Days              : 0
Hours             : 5
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 180000000000
TotalDays         : 0.208333333333333
TotalHours        : 5
TotalMinutes      : 300
TotalSeconds      : 18000
TotalMilliseconds : 18000000

and the requested driver date/time =

Date        : 12/15/2019 12:00:00 AM
Day         : 15
DayOfWeek   : Sunday
DayOfYear   : 349
Hour        : 18
Kind        : Local
Millisecond : 0
Minute      : 0
Month       : 12
Second      : 0
Ticks       : 637120296000000000
TimeOfDay   : 18:00:00
Year        : 2019
DateTime    : Sunday, December 15, 2019 6:00:00 PM
jantari commented 3 years ago

And what about this if you don't mind:

Get-PnpDevice |
    Where-Object { $_.HardwareId -like "PCI\VEN_8086&DEV_15BF*" } |
    Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_DriverInfPath' |
    Foreach-Object {
        $details = Get-WindowsDriver -Online -Driver $_.Data
        $details
        $details.Date.Kind
        Select-String -LiteralPath $details.OriginalFileName -Pattern "^DriverVer"
    } | Format-List
osxdude commented 3 years ago

This one's a doozy. It isn't pasting right into a text file, and the CSV (vs fl) won't upload. So I changed the extension to txt on the csv. Get-PnpDevice.txt

jantari commented 3 years ago

Thank you for your help, you don't have to export it to a CSV for me. Just the output as it is in the console is fine - in fact in this case the conversion removed some information, but it was enough that I can see the driver date when retrieved with this method is listed as 12/16/2019 12:00:00 AM so Get-PnpDeviceProperty does indeed add a local timezone offset which flips the date back to 12/15/2019 in your case, and that's why LSUClient thinks your drivers are outdated.

I googled a bit, and I think the offset being applied to DateTime values coming from WMI/CIM is normal (see Remarks)

Could you run this:

$DriverDateLocalTZ = Get-PnpDevice |
    Where-Object { $_.HardwareId -like "PCI\VEN_8086&DEV_15BF*" } |
    Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_DriverDate' |
    Select -ExpandProperty Data

$DriverDateLocalTZ.ToUniversalTime()
[System.DateTimeOffset]::new($DriverDateLocalTZ)
osxdude commented 3 years ago

Understood, here's the result of that command.

Monday, December 16, 2019 12:00:00 AM

DateTime      : 12/15/2019 6:00:00 PM
UtcDateTime   : 12/16/2019 12:00:00 AM
LocalDateTime : 12/15/2019 6:00:00 PM
Date          : 12/15/2019 12:00:00 AM
Day           : 15
DayOfWeek     : Sunday
DayOfYear     : 349
Hour          : 18
Millisecond   : 0
Minute        : 0
Month         : 12
Offset        : -06:00:00
Second        : 0
Ticks         : 637120296000000000
UtcTicks      : 637120512000000000
TimeOfDay     : 18:00:00
Year          : 2019
jantari commented 3 years ago

Thanks, I've thought about this for a while and I think it's best if I just call .ToUniversalTime() on the driver datetime being returned by WMI/CIM. I explored some alternatives but the only methods to get the DriverDate without an offset applied I've found are through Get-WindowsDriver which is a DISM cmdlet that requires elevation to run and is rather slow, or by using the native SetupApi APIs e.g. SetupDiGetDeviceProperty that would require a ton of boilerplate code and work to call from PowerShell.

Because most of the discussion in this thread ended up being about the driver date timezone issue, I'll rename the issue to reflect that and open a separate one for the PowerShell window being hidden during Get-LSUpdate runs problem, that way I can refer to them individually in comments, commits and the release notes for the next version.

I've just pushed the fix for the timezone problem aka lots of drivers that are already installed being returned as "still needed" by LSUClient to the develop branch and it'll be in the next release. I'm not sure about the window hiding/seemingly closing because I still have a good bit of testing to do around that.

Thanks for the help troubleshooting this, it's a bug I really didn't expect and that I also would have never found because I have a positive timezone offset compared to UTC and adding a few hours to midnight still always ends up being the same date!

jantari commented 3 years ago

The fix for this is now live in Version 1.2.5 :tada:

I wanted to get this and a few other things released before I dive into #34

osxdude commented 3 years ago

Sweet. Thanks!!