You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PowerShell中正确将FILETIME类型转换为DATETIME

问题背景
  • 参考此前发布的相关主题
  • 正在开发的解决方案需要在Windows Server环境中通过PowerShell运行,底层依托.NET Framework实现。
  • 由于微软PKI模块未提供对应功能的专用cmdlet,查找了可实现相关操作的Windows原生函数,最终在wincrypt.h、timezoneapi.h、minwinbase.h头文件中找到了目标接口。
  • 微软官方文档中的代码示例均为C++编写,需要将Managed Types及其他数据类型转换为C#定义,才能将相关函数与结构体集成到PowerShell脚本中。
  • 目前所有操作FILETIME类型属性或参数的尝试均失败,试了多种时间纪元起点计算方式,得到的结果始终不符合预期,测试用的纪元定义代码如下:
$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)  
  • 现需要正确实现FILETIME到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 

<# 
# 如果CRL_INFO结构体的ThisUpdate和NextUpdate属性声明为IntPtr类型,需要用FILETIME结构体将指针转换为底层结构。  

$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'                  # 待检索的PKI对象地址
$pszObjectOid     = 2                     # CONTEXT_OID_CRL 
$dwRetrievalFlags = 4                     # CRYPT_WIRE_ONLY_RETRIEVAL 
$dwTimeout        = 0                     # 设为0时函数不会超时
$ppvObject        = [System.IntPtr]::Zero # 返回对象的指针地址
$hAsyncRetrieve   = [System.IntPtr]::Zero # 保留参数,必须设为NULL
$pCredentials     = [System.IntPtr]::Zero # 未使用参数
$pvVerify         = [System.IntPtr]::Zero # 设为NULL表示调用方不需要获取签名者的证书上下文或索引
$pAuxInfo         = [System.IntPtr]::Zero # 非NULL且设置了结构体cbSize成员时,返回上次成功有线检索的时间

$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 
} 
解决方案

你的代码有两处核心错误,直接导致时间转换结果异常:

  • 内存释放时机错误:在读取CRL_CONTEXT.pCrlInfo指向的结构体内容之前,就调用CertFreeCRLContext释放了上下文,对应内存会被系统回收,后续Marshal读取到的是无效内存里的乱码数据,时间值自然不符合预期。必须先读完CRL_INFO结构体,再释放CRL上下文。
  • 位运算类型转换错误:dwLowDateTime是32位无符号整数,直接将其转为Int32时,若值超过2^31-1会触发符号位扩展,拼接出来的64位FILETIME值完全错误。拼接时需要先将dwLowDateTime转为UInt32,再参与位运算。

不需要自己额外定义FILETIME结构体,你当前引用的System.Runtime.InteropServices.ComTypes.FILETIME已经是系统内置的正确定义,可直接使用;也不需要手动做纪元加减,DateTime.FromFileTimeUtc本身已经内置了1601年纪元的转换逻辑。

修正后的核心执行代码如下:

if ($CryptRetrieveObjectByUrl) 
{ 
    $CRL_CONTEXT = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ppvObject,[System.Type][Win32.Crypt32+CRL_CONTEXT]) 
    # 先读取结构体内容,再释放上下文
    $CRL_INFO = [System.Runtime.InteropServices.Marshal]::PtrToStructure($CRL_CONTEXT.pCrlInfo,[System.Type][Win32.Crypt32+CRL_INFO])  
    [System.Void][Win32.Crypt32]::CertFreeCRLContext($ppvObject) 

    # 按无符号值拼接高低位,避免符号位错误
    $NextUpdate = [DateTime]::FromFileTimeUtc(([Int64]$CRL_INFO.NextUpdate.dwHighDateTime -shl 32) -bor [UInt32]$CRL_INFO.NextUpdate.dwLowDateTime)
    $ThisUpdate = [DateTime]::FromFileTimeUtc(([Int64]$CRL_INFO.ThisUpdate.dwHighDateTime -shl 32) -bor [UInt32]$CRL_INFO.ThisUpdate.dwLowDateTime)

    # 输出结果
    $ThisUpdate
    $NextUpdate
} 

内容的提问来源于stack exchange,提问作者Kanuc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:36:26