Creating self-signed PowerShell scripts start to finish - powershell

While investigating this problem myself, I was unable to find any start-to finish solutions on self-signing PowerShell scripts. So, how do you handle it all self-contained within PowerShell itself to utilize the AllSigned Execution Policy and help secure your systems better?

This is a modern approach to self-signed PowerShell scripts. I found older versions of PowerShell ISE (only confirmed 2.0) will encode your scripts in Big Endian vs UTF-8 and cause issues with signing. With this method, you shouldn't run into that since we're on v4+ here.
Requirement: PoSH 4.0+.
This function will: check if a Pfx cert exists and import it to LocalMachine\TrustedPublisher; check if a cert was passed to it, export to a Pfx cert and import it; or create the cert to LocalMachine\Personal, export it, and import it. I was unable to get the permissions to work with me to use the Cert:\CurrentUser stores outside of \My(Personal).
$ErrorActionPreference = 'Stop'
Function New-SelfSignedCertificate
{
Param([Parameter(Mandatory=$True)]$PfxCertPath,$CertObj)
# Creates a SecureString object
$Cred = (Get-Credential).Password
If (Test-Path $PfxCertPath)
{
Try {
Import-PfxCertificate -FilePath $PfxCertPath -Password $Cred -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
Write "$($PfxCertPath.FriendlyName) exists and is valid. Imported certificate to TrustedPublishers"
} Catch {
Write "Type mismatch or improper permission. Ensure your PFX cert is formed properly."
Write "[$($_.Exception.GetType().FullName)] $($_.Exception.Message)"
}
} ElseIf ($CertObj) {
Try {
Export-PfxCertificate -Cert $CertObj -FilePath $PfxCertPath -Password $Cred -Force
Import-PfxCertificate -FilePath $PfxCertPath -Password $Cred -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
} Catch {
Write "[$($_.Exception.GetType().FullName)] $($_.Exception.Message)"
}
} Else {
Try {
$DNS = "$((GWMI Win32_ComputerSystem).DNSHostName).$((GWMI Win32_ComputerSystem).Domain)"
$CertObj = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName $DNS -Type CodeSigningCert -FriendlyName 'Self-Sign'
Export-PfxCertificate -Cert $CertObj -FilePath $PfxCertPath -Password $Cred -Force
Import-PfxCertificate -FilePath $PfxCertPath -Password $Cred -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
} Catch {
Write "[$($_.Exception.GetType().FullName)] $($_.Exception.Message)"
}
}
}
# Can be called like:
# Sign-Script -File C:\Script.ps1 -Certificate (GCI Cert:\LocalMachine\TrustedPublisher -CodeSigningCert)
#
# After the cert is imported to TrustedPublisher, you can use the
# exported pfx cert to sign on the machine instead of this method
Function Sign-Script
{
Param($File,$Cert)
If($Cert-is[String]){Try{$Cert=Get-PfxCertificate("$Cert")}Catch{}}
Set-AuthenticodeSignature -FilePath $File -Certificate $Cert -Force
}
Function Check-SignedScript
{
Param($File)
Get-AuthenticodeSignature -FilePath $File
}
After all is said and done, you can execute Set-ExecutionPolicy AllSigned as admin and use this script to sign all your scripts. Check-SignedScript will tell you if the sign is valid and you can tell if Sign-Script worked as your file will have # SIG # Begin signature block at the end. Any edits to a signed script need to be re-signed in order to execute.

Related

New-PSDrive's "-Persist" flag not working: drives removed on reboot

The script mounts the drive correctly, but the drive is not persisted after rebooting the machine:
function RemapDrive {
param(
$DriveLetter,
$FullPath,
$Credential
)
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
# $DriveLetter must be concatenated with ":" for the command to work
net use "${DriveLetter}:" /del
## $DriveLetter cannot contain ":"
$psDrive = New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Credential $Credential -Scope "Global" -Persist
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X" -FullPath "\\my-server\x" -Credential $credential
What I have found:
“When you scope the command locally, that is, without dot-sourcing, the Persist parameter does not persist the creation of a PSDrive beyond the scope in which you run the command. If you run New-PSDrive inside a script, and you want the new drive to persist indefinitely, you must dot-source the script. For best results, to force a new drive to persist, specify Global as the value of the Scope parameter in addition to adding Persist to your command.”
I have tried executing the script with ". .\my-script.ps1" (to dot-source the script?), but the result is the same.
Playing around with "net use" and the registry to try to add the network drive has lead me to a cul-de-sac as well.
Specs:
Windows 10 Home
Powershell version:
Major Minor Build Revision
----- ----- ----- --------
5 1 18362 1171
Basically, New-PSDrive doesn't have the /SAVECRED parameter from net use, and will not persistently map drives as a user other than the one running the script.
There are three ways to handle this:
[Recommended] Fix the file share permissions instead of using a separate username/password, then use New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Scope 'Global' -Persist with no credential flag. This assumes your file share allows kerberos logins, so may not work in some edge cases.
Use net use, and include the username, password, /persistent:yes and /savecred. This can be done in powershell without any issues.
Set the powershell script you already have to run at startup.
Set up your script to use the credential manager - see the answer here
Install the CredentialManager powershell module
set HKCU\Network\[drive letter]\ConnectionType = 1
set HKCU\Network\[drive letter]\DeferFlags= 4
What finally work was user19702's option #2, with a bit of extra work regarding the registration of the username and the password.
WARNING: as he mentioned, the best option (option #1) would have been "fixing the file share permissions instead of using a separate username/password". This was not possible in my case, and this is why I had to go with option #2.
This is the script:
# ---
# Helper functions:
function RemapDrive {
param(
$DriveLetter,
$Server,
$FullPath,
$Credential
)
# For net.exe to work, DriveLetter must end with with ":"
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
net use "$DriveLetter" /del
# "net use" requires username and password as plain text
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$Username=$Credential.Username
Write-Host "Registring credentials for server '$Server' ..."
cmdkey /add:$Server /user:$Username /pass:$Password
Write-Host "Mapping the drive ..."
net use $DriveLetter $FullPath /persistent:yes i
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
# ---
# Process to execute:
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X:" -Server "my-server" -FullPath "\\my-server\x" -Credential $credential
If you do not want to use a hardcoded password in BuildCredential, but you want to prompt the user instead:
function GetCredential {
param(
$Label
)
$credential = Get-Credential -Message "Write your credentials for '$Label':"
if(!$credential) {
throw "A credential was needed to continue. Process aborted."
}
return $credential
}
Also, if instead of using $Server as a param, you want to extract it from $FullPath using regex, you can do that.
It presumes the $FullPath has the following format: \\server-name\dir1\dir2\etc
# Get server name using regex:
$FullPath -match '\\\\(.*?)\\.*?'
$Server = $Matches[1]

Import certificate to the Group Policy store with PowerShell

I am building ARM-templates to set up test-environments in Azure. I am using DSC to set up the different machines. One thing I want to automate is to import a certificate to the group-policies. You can do it like this manually on the domain-controller (Active-Directory server):
Group Policy Management -> Forest: mydomain.net -> Domains -> mydomain.net -> Group Policy Objects -> Default Domain Policy
Right click -> Edit
Default Domain Policy -> Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Public Key Policies -> Trusted Root Certification Authorities
Right click -> Import
I have laborated with Import-PfxCertificate, CertUtil.exe and .NET C# to accomplish it but haven’t succeeded. What I have tested you can see below, I have put some comments about my thoughts.
Can anyone help me? How should I do this?
First we create a certificate and export it and finally we delete it (we keep the exported one):
$certificateStoreLocation = "CERT:\LocalMachine\My";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
$certificate = New-SelfSignedCertificate -CertStoreLocation $certificateStoreLocation -DnsName "Test-Certificate";
$certificateLocation = "$($certificateStoreLocation)\$($certificate.Thumbprint)";
$result = Export-PfxCertificate -Cert $certificateLocation -FilePath "C:\Data\Certificates\Test-Certificate.pfx" -Password $password;
Get-ChildItem $certificateLocation | Remove-Item;
List the certificate stores
foreach($item in Get-ChildItem "CERT:\")
{
Write-Host " - CERT:\$($item.Location)\";
foreach($store in $item.StoreNames.GetEnumerator())
{
Write-Host " - CERT:\$($item.Location)\$($store.Name)";
}
}
PowerShell – Import-PfxCertificate
$certificateStoreLocation = "CERT:\LocalMachine\Root";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
Import-PfxCertificate -CertStoreLocation $certificateStoreLocation -FilePath "C:\Data\Certificates\Test-Certificate.pfx" -Password $password;
Get-ChildItem $certificateStoreLocation;
# Now you can find the certificate in the MMC Certificate Snapin:
# [Console Root\Certificates (Local Computer)\Trusted Root Certification Authorities\Certificates]
# Now you can find the certificate in the registry.
# Get-ChildItem "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\";
# I want to put the certificate here:
# Get-ChildItem "REGISTRY::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\";
PowerShell – CertUtil
CertUtil -p "P#ssword12" -ImportPfx "Root" "C:\Data\Certificates\Test-Certificate.pfx";
Get-ChildItem "CERT:\LocalMachine\Root";
CertUtil -p "P#ssword12" -ImportPfx -GroupPolicy "Root" "C:\Data\Certificates\Test-Certificate.pfx"; # No error but the same result as CertUtil -p "P#ssword12" -ImportPfx "Root" "C:\Data\Certificates\Test-Certificate.pfx".
.NET C#
using(var certificate = new X509Certificate2(#"C:\Data\Certificates\Test-Certificate.pfx", "P#ssword12"))
{
// We only have StoreLocation.CurrentUser and StoreLocation.LocalMachine.
// Can I use System.Management.Automation.Security.NativeMethods+CertStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
// somehow to create/open a store by calling new X509Store(IntPtr storeHandle).
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
}
}
My imaginary solution
Thought this was a possible solution:
Import the certificate to “CERT:\LocalMachine\Root”
Move the registry-key “HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT” to “HKLM:\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT”
Restart the machine
The registry-keys get correct but the localmachine-root-certificate is still in the certificate-mmc-snapin and no root-certificate is found in the Group Policy Management console.
$certificateRegistryKeyPathPrefix = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\";
$certificateStoreLocation = "CERT:\LocalMachine\Root";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
$pfxCertificatePath = "C:\Data\Certificates\Test-Certificate.pfx";
$policyCertificateRegistryKeyPathPrefix = "HKLM:\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\";
# Get the thumbprint from the pfx-file so we can check if it's already in the registry.
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2;
$certificate.Import($pfxCertificatePath, $password, "DefaultKeySet");
$policyCertificateRegistryKeyPath = "$($policyCertificateRegistryKeyPathPrefix)$($certificate.Thumbprint)";
$policyCertificateRegistryKey = Get-Item -ErrorAction SilentlyContinue -Path $policyCertificateRegistryKeyPath;
if(!$policyCertificateRegistryKey)
{
$certificateRegistryKeyPath = "$($certificateRegistryKeyPathPrefix)$($certificate.Thumbprint)";
$certificateRegistryKey = Get-Item -ErrorAction SilentlyContinue -Path $certificateRegistryKeyPath;
if(!$certificateRegistryKey)
{
$certificate = Import-PfxCertificate -CertStoreLocation $certificateStoreLocation -FilePath $pfxCertificatePath -Password $password;
$certificateRegistryKey = Get-Item -Path $certificateRegistryKeyPath;
}
Move-Item -Destination $policyCertificateRegistryKeyPath -Path $certificateRegistryKeyPath;
# And then we need to reboot the machine.
}
Instead of trying to directly modify the registry under the 'Policies' path, create or modify an 'Registry.pol' file to populate it.
You could use the 'PolicyFileEditor' module from the PowerShell Gallery to do this, but the easiest way is to use the native GroupPolicy module to create and set a Registry.pol file as part of a domain GPO, which will also push out the certificate to member servers.
On a domain controller, import/create a cert so it has a blob value stored in the registry, create a GPO, then run something like the following to configure the GPO (named "DistributeRootCerts" here):
$certsGpoName = 'DistributeRootCerts'
$certThumbprint = '3A8E60952E2CDB7A31713258468A8F0C7FB3C6F6'
$certRegistryKeyPath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates\{0}' -f $certThumbprint
$certBlob = Get-ItemProperty -Path $certRegistryKeyPath -Name 'Blob' | Select -Expand 'Blob'
$certPoliciesRegistryKey = 'HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates\{0}' -f $certThumbprint
$null = Set-GPRegistryValue -Name $certsGpoName -Key $certPoliciesRegistryKey -ValueName 'Blob' -Type Binary -Value $certBlob
Then you just need to link the GPO into production with 'New-GPLink'.

Calling Script Remotely Does Not Work

I can run this script perfectly on my SharePoint server, and the user's profile picture gets updated:
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")
$siteurl = "http://SHAREPOINTSITE/"
try {
$site = New-Object Microsoft.SharePoint.SPSite($siteurl)
} catch {
New-Item -ItemType File -Path C:\Users\admin\Desktop -Name ERROR1.txt -Value $_.Exception.Message -Force
}
try {
$context = [Microsoft.Office.Server.ServerContext]::GetContext($site)
} catch {
New-Item -ItemType File -Path C:\Users\admin\Desktop -Name ERROR2.txt -Value $_.Exception.Message -Force
}
#This gets the User Profile Manager which is what we want to get hold of the users
$upm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($context)
$user = "DOMAIN\user.name"
#Put it in a loop for iterating for all users
if ($upm.UserExists($user)) {
try {
$profile = $upm.GetUserProfile($user)
$profile["PictureURL"].Value = "\\Sharepoint\C$\Users\admin\Desktop\1.jpg";
$profile.Commit();
} catch {
New-Item -ItemType File -Path C:\Users\admin\Desktop -Name ERROR3.txt -Value $_.Exception.Message -Force
}
}
New-Item -ItemType File -Path C:\Users\admin\Desktop -Name HELLO.txt -Force
$site.Dispose()
But when I run it from a remote PowerShell session, I am getting some weird errors:
ERROR1.txt
Exception calling ".ctor" with "1" argument(s): "The Web application at http://SHAREPOINTSITE/ could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application."
ERROR2.txt
Multiple ambiguous overloads found for "GetContext" and the argument count: "1".
I have checked all of the possibilities here, but still seeing this issue.
This is how I call the above script from the remote machine:
$spfarm = "DOMAIN\admin.username"
$spfarmpw = ConvertTo-SecureString "password123" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $spfarm,$spfarmpw
$session = New-PSSession SharePoint -Authentication Default -Credential $cred
Invoke-Command -Session $session -FilePath "\\SharePoint\C$\Users\admin\Desktop\testremote.ps1"
I have tried calling this in a few different ways (e.g. hosting the script on my machine or hosting it on the SharePoint server, as well as using relative paths to call the script), but I always see these errors.
Can anyone please help me understand why this doesn't work when calling it from a remote PC? The script is clearly being called (HELLO.txt always gets created), but the SharePoint profile picture never gets updated - even though that script definitely should work.
Any help or guidance is much appreciated
nslookup
nslookup SHAREPOINTSITE
Output
Server: dc1.domain.co.uk
Address: xx.xx.x.xx
Name: sharepoint.domain.co.uk
Address: yy.yy.y.yy
Aliases: SHAREPOINTSITE.domain.co.uk
Where yy.yy.y.yy is the correct IP (it's the same address I see when executing ping SHAREPOINTSITE)
Try changing the Authentication method to CredSSP. This is required by the remote PowerShell so that it can pass the credentials on.

Install certificate with PowerShell on remote server

I want to install a certificate (X.509) created with makecert.exe on a remote server. I am not able to use psexec or something like that but have to use PowerShell.
Server operating system: Windows Server 2008 R2
PowerShell version: 4
Question: How to install a certificate with PowerShell on a remote server.
Scenario: ServerA has the SSL cert, ServerB would like the SSL cert imported
define two variables (ServerB only):
$afMachineName = "SomeMachineNameOrIp"
$certSaveLocation = "c:\temp\Cert.CER"
enable trust on both machines (ServerA & ServerB):
Function enableRemotePS() {
Enable-PSRemoting -Force
Set-Item wsman:\localhost\client\trustedhosts $afMachineName -Force
Restart-Service WinRM
}
Save the certificate (ServerB only):
Function saveCert([string]$machineName,[string]$certSaveLocation) {
Invoke-Command -ComputerName $machineName -ArgumentList $certSaveLocation -ScriptBlock {
param($certSaveLocation)
$cert = dir Cert:\LocalMachine\Root | where {$_.Subject -eq "CN=YOURCERTNAME" };
$certBytes = $cert.Export("cert");
[system.IO.file]::WriteAllBytes($certSaveLocation, $certBytes);
}
Copy-Item -Path \\$machineName\c$\temp\CertAF.CER -Destination $certSaveLocation
}
Import the certificate (ServerB only)
Function importCert([string]$certSaveLocation) {
$CertToImport = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certSaveLocation
$CertStoreScope = "LocalMachine"
$CertStoreName = "Root"
$CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store $CertStoreName, $CertStoreScope
# Import The Targeted Certificate Into The Specified Cert Store Name Of The Specified Cert Store Scope
$CertStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$CertStore.Add($CertToImport)
$CertStore.Close()
}
To import a PFX file you can use Import-PfxCertificate, for example
Import-PfxCertificate -FilePath YOUR_PFX_FILE.pfx -Password (ConvertTo-SecureString -String "THE_PFX_PASSWORD" -AsPlainText -Force)
To do this on a remote computer, you can use Invoke-Command -ComputerName (and use an UNC path for the PFX file).

How to fill the prompt in powershell script

I use a command like this:
get-pfxcertificate C:\test.pfx
Enter password: *******
The command ask me to fill the prompt. But I can't do that in my script (test.ps1 for ex)
What I need is like this:
get-pfxcertificate C:\test.pfx -password "123456"
or something similar so I can run my script without fill in the prompt each time
I'm very thankful for any reply
There's no Password parameter, you can try with a .NET class:
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import('C:\test.pfx','123456','DefaultKeySet')
Another option is to extend the abilities of Get-PfxCertificate, essentially enabling the password to be passed in.
# create a backup of the original cmdlet
if(Test-Path Function:\Get-PfxCertificate){
Copy Function:\Get-PfxCertificate Function:\Get-PfxCertificateOriginal
}
# create a new cmdlet with the same name (overwrites the original)
function Get-PfxCertificate {
[CmdletBinding(DefaultParameterSetName='ByPath')]
param(
[Parameter(Position=0, Mandatory=$true, ParameterSetName='ByPath')] [string[]] $filePath,
[Parameter(Mandatory=$true, ParameterSetName='ByLiteralPath')] [string[]] $literalPath,
[Parameter(Position=1, ParameterSetName='ByPath')]
[Parameter(Position=1, ParameterSetName='ByLiteralPath')] [string] $password,
[Parameter(Position=2, ParameterSetName='ByPath')]
[Parameter(Position=2, ParameterSetName='ByLiteralPath')] [string]
[ValidateSet('DefaultKeySet','Exportable','MachineKeySet','PersistKeySet','UserKeySet','UserProtected')] $x509KeyStorageFlag = 'DefaultKeySet'
)
if($PsCmdlet.ParameterSetName -eq 'ByPath'){
$literalPath = Resolve-Path $filePath
}
if(!$password){
# if the password parameter isn't present, just use the original cmdlet
$cert = Get-PfxCertificateOriginal -literalPath $literalPath
} else {
# otherwise use the .NET implementation
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($literalPath, $password, $X509KeyStorageFlag)
}
return $cert
}
And now you can call it
# tada: extended cmdlet with `password` parameter
Get-PfxCertificate 'C:\path\to\cert.pfx' 'password'
Also, if you still need the prompt, you can do something like this.
$pwd = Read-Host 'Please enter your SSL Certificate password.'
Get-PfxCertificate 'C:\path\to\cert.pfx' $pwd
There is now a Get-PfxData command in PowerShell that gets the certificate and chain. The command includes a -Password parameter that takes a SecureString object so you can avoid being prompted.
The EndEntityCertificates property contains an array of certificates at the end of the certificate chain and will contain the same certificate object created by the Get-PfxCertificate command.
The following example converts a normal string to a SecureString object, loads the certificates from a file, then assigns the first/only end certificate to the $SigningCert variable:
$SecurePassword=ConvertTo-SecureString -String "MyPassword" -AsPlainText -Force
$PfxData=Get-PfxData -FilePath ".\cert_filename.pfx" -Password $SecurePassword
$SigningCert=$PfxData.EndEntityCertificates[0]
You can now apply $SigningCert without being prompted for the password.
Thanks to Shay for pointing me in the right direction.
My need was getting the Thumbprint from the PFX file, so I used the non-persistent DefaultKeySet.
Testing under 2012 PS3 fails unless the key set is fully qualified.
Also, a blank space between Import and left parenthesis, i.e. "$cert.Import^^(Sys..." causes an error. Picky, picky parser.
My PFX password is encrypted in the source.
I decrypt it at runtime so it is not visible in the souce.
Set-StrictMode -Version Latest
[string] $strPW = '123456'
[string] $strPFX = 'C:\MyCert.pfx'
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($strPFX,$strPW,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"DefaultKeySet")
$cert.Thumbprint
If you are forced to use Windows Powershell (v5.*), then previous solutions in the thread are for you.
However if you can use Powershell 7/Powershell Core, then its own Get-PfxCertificate command does have a -Password parameter.
After installing Powershell 7 and making sure to use pwsh and not powershell, you can load the certificate that way:
$certificate = (Get-PfxCertificate <certificate_path>.pfx -Password (ConvertTo-SecureString -String "<password>" -AsPlainText -Force))
This also works using native PowerShell instead of .NET:
$securePassword = ConvertTo-SecureString -String $strPW -Force -AsPlainText
$cert = Import-PfxCertificate -FilePath $strPFX cert:\LocalMachine\My -Password $securePassword