Home > Back-end >  How to convert FILETIME to DATETIME in PowerShell?
How to convert FILETIME to DATETIME in PowerShell?

Time:07-19

In reference to one of my topics

The solution I’m trying to implement will run in Windows Server through PowerShell, in other words, with .NET Framework behind the scenes.

In the absence of any specific cmdlet in Microsoft's PKI module, I’ve been looking for native Windows functions that could perform the operations and I’ve found them in the headers wincrypt.h, timezoneapi.h, minwinbase.h.

The code snippets in the Microsoft documentation are written in C ; therefore, converting Managed Types and other Data Types to C# is required to integrate functions and structures into the PowerShell script.

Unfortunately, every attempt to interact with properties or parameters of the FILETIME type have failed.

I’ve tried different starting points in time, but the results are still not the ones expected

$PowerShell = [DateTime]::new(0001,1,1,0,0,0,[System.DateTimeKind]::Utc) 
$FileTime = [DateTime]::new(1601,1,1,0,0,0,[System.DateTimeKind]::Utc) 
$UnixEpoch = [DateTime]::new(1970,1,1,0,0,0,[System.DateTimeKind]::Utc)  

Could anyone point me in the right direction to properly convert FILETIME to DATETIME?

$Cryptnet =  
@' 
[DllImport("Cryptnet.dll", CharSet = CharSet.Auto, SetLastError = true)] 
public static extern bool CryptRetrieveObjectByUrl 
( 
        string pszUrl, 
        uint   pszObjectOid, 
        uint   dwRetrievalFlags, 
        uint   dwTimeout, 
    ref IntPtr ppvObject, 
        IntPtr hAsyncRetrieve, 
        IntPtr pCredentials, 
        IntPtr pvVerify, 
        IntPtr pAuxInfo 
); 
'@ 

Microsoft.PowerShell.Utility\Add-Type -MemberDefinition $Cryptnet -Name Cryptnet -Namespace Win32 

$Crypt32 =  
@' 
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
public static extern bool CertFreeCRLContext 
( 
    IntPtr pCrlContext 
); 

[StructLayout(LayoutKind.Sequential)] 
public struct CRL_CONTEXT 
{ 
    public uint      dwCertEncodingType; 
    public IntPtr    pbCrlEncoded; 
    public uint      cbCrlEncoded; 
    public IntPtr    pCrlInfo; 
    public IntPtr    hCertStore; 
}; 

[StructLayout(LayoutKind.Sequential)] 
public struct CRL_INFO 
{ 
    public uint                                             dwVersion; 
    public IntPtr                                           SignatureAlgorithm; 
    public IntPtr                                           Issuer; 
    public System.Runtime.InteropServices.ComTypes.FILETIME ThisUpdate; 
    public System.Runtime.InteropServices.ComTypes.FILETIME NextUpdate; 
    public uint                                             cCRLEntry; 
    public IntPtr                                           rgCRLEntry; 
    public uint                                             cExtension; 
    public IntPtr                                           rgExtension; 
}; 
'@ 

Microsoft.PowerShell.Utility\Add-Type -MemberDefinition $Crypt32 -Name Crypt32 -Namespace Win32 

<# 
# if the ThisUpdate and NextUpdate properties of the CRL_INFO struct are declared as IntPtr, the FILETIME struct is required to convert the pointers to the underlying structure.  

$Kernel32 =  
@' 
[StructLayout(LayoutKind.Sequential, Pack = 4)] 
public struct FILETIME 
{ 
  public uint dwLowDateTime; 
  public uint dwHighDateTime; 
}; 
'@ 

Microsoft.PowerShell.Utility\Add-Type -MemberDefinition $Kernel32 -Name Kernel32 -Namespace Win32 
#> 

$pszUrl           = 'http://pki.contoso.com/cadp/Contoso-RootCA.crl'                  # [...] The address of a PKI object to be retrieved. [...] 
$pszObjectOid     = 2                     # CONTEXT_OID_CRL 
$dwRetrievalFlags = 4                     # CRYPT_WIRE_ONLY_RETRIEVAL 
$dwTimeout        = 0                     # [...] If a value of zero is specified, this function does not time out. [...] 
$ppvObject        = [System.IntPtr]::Zero # [...] The address of a pointer to the returned object. [...] 
$hAsyncRetrieve   = [System.IntPtr]::Zero # [...] This parameter is reserved and must be set to NULL. [...] 
$pCredentials     = [System.IntPtr]::Zero # [...] This parameter is not used. [...] 
$pvVerify         = [System.IntPtr]::Zero # [...] It can be NULL to indicate that the caller is not interested in getting the certificate context or index of the signer [...] 
$pAuxInfo         = [System.IntPtr]::Zero # [...] If not NULL and if the cbSize member of the structure is set, this parameter returns the time of the last successful wire retrieval. [...] 

$CryptRetrieveObjectByUrl = [Win32.Cryptnet]::CryptRetrieveObjectByUrl( 
$pszUrl, 
$pszObjectOid, 
$dwRetrievalFlags, 
$dwTimeout, 
[ref]$ppvObject, 
$hAsyncRetrieve, 
$pCredentials, 
$pvVerify, 
$pAuxInfo 
) 

if ($CryptRetrieveObjectByUrl) 
{ 
    $CRL_CONTEXT = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ppvObject,[System.Type][Win32.Crypt32 CRL_CONTEXT]) 

    [System.Void][Win32.Crypt32]::CertFreeCRLContext($ppvObject) 

    $CRL_INFO = [System.Runtime.InteropServices.Marshal]::PtrToStructure($CRL_CONTEXT.pCrlInfo,[System.Type][Win32.Crypt32 CRL_INFO])  

    $NextUpdate = [System.Int64]($CRL_INFO.NextUpdate.dwHighDateTime -shl 32) -bor [System.Int32]($CRL_INFO.NextUpdate.dwLowDateTime) 

    [System.DateTime]::FromFileTimeUtc($NextUpdate) 

    $ThisUpdate = ($CRL_INFO.ThisUpdate.dwHighDateTime -shl 32) -bor ($CRL_INFO.ThisUpdate.dwLowDateTime) 

    [System.DateTime]::FromFileTimeUtc($ThisUpdate) 

} 

else 
{ 
    $LastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 
    $Exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $LastWin32Error 
    $ErrorId = 'CryptRetrieveObjectByUrl' 
    $ErrorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified 
    $TargetObject= $null 
    $ErrorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList 
    $Exception, 
    $ErrorId, 
    $ErrorCategory, 
    $TargetObject 

    Write-Error -ErrorRecord $ErrorRecord 
} 

CodePudding user response:

You can avoid the challenges of converting your FILETIME struct instances to [long] values, as required by the System.DateTime.FromFileTimeUtc method, in PowerShell code, by instead providing the conversion as part of your ad hoc-compiled C# code:

$Kernel32 =  
@' 
[StructLayout(LayoutKind.Sequential, Pack = 4)] 
public struct FILETIME 
{ 
  public uint dwLowDateTime; 
  public uint dwHighDateTime; 
}; 

// Converts a FILETIME instance to a UTC DateTime instance
public static DateTime ToDateTime(FILETIME ft) {
  long ftAsLong = unchecked(
    (long)((ulong)ft.dwHighDateTime << 32 | ft.dwLowDateTime)
  );
  return DateTime.FromFileTimeUtc(ftAsLong);
}
'@ 

Microsoft.PowerShell.Utility\Add-Type -MemberDefinition $Kernel32 -Name Kernel32 -Namespace Win32 

# Sample call

# Sample FILETIME value.
$ft = [Win32.Kernel32 FILETIME]::new(); 
$ft.dwLowDateTime = 4280342244;
$ft.dwHighDateTime = 30971996

# Convert to [datetime] (as a UTC time stamp; .Kind == Utc)
[Win32.Kernel32]::ToDateTime($ft)

You can provide an analogous method for the .NET System.Runtime.InteropServices.ComTypes.FILETIME value type, whose members are actually int (Int32)-typed.

  • Related