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.