Install certificate with PowerShell on remote server - powershell

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).

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]

How to use PowerShell for a IPSec VPN IKEv2 connection?

To test an IPSec connection, I've used a client implementation StrongSwan with Ubuntu 16 without UI.
Is it possible to use only PowerShell to create and test the VPN connection?
Available assets:
public VPN endpoint i.e. IP
user name
password
PSK (private shared key)
This script is for cert-auth but you can modify:
# Set these to the correct values
$server_address = "vpn.example.com"
$connection_name = "VPN Connection"
$certificate_path = "certificate.p12"
$ca_cert_path = "strongswanCert.pem"
$password = ConvertTo-SecureString -String "P12 passphrase" -AsPlainText -Force
# Import machine cert
Import-PfxCertificate -FilePath $certificate_path -CertStoreLocation Cert:\LocalMachine\My\ -Password $password
# Import CA root
Import-Certificate -FilePath $ca_cert_path -CertStoreLocation Cert:\LocalMachine\Root\
# Add VPN connection IKEv2 with machine cert
Add-VpnConnection -Name $connection_name -ServerAddress $server_address -TunnelType Ikev2 -EncryptionLevel Required -AuthenticationMethod MachineCertificate -AllUserConnection
# Add IPv6 default route (::/0 does not work)
Add-VpnConnectionRoute -ConnectionName $connection_name -DestinationPrefix ::/1
Add-VpnConnectionRoute -ConnectionName $connection_name -DestinationPrefix 8000::/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'.

Creating self-signed PowerShell scripts start to finish

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.

rdesktop shell escaping issue

I'm trying to send this:
Get-WmiObject Win32_PNPEntity |Where{$_.DeviceID.StartsWith("PCI\VEN_10DE") -or $_.DeviceID.StartsWith("PCI\VEN_1002")}
over rdesktop like:
rdesktop -a8 209.** -u ** -p ** -s "cmd.exe /K powershell.exe Get-WmiObject Win32_PNPEntity |Where{\$_.DeviceID.StartsWith("PCI\VEN_10DE") -or $_.DeviceID.StartsWith("PCI\VEN_1002")}"
But windows' shell says:
'Where{$_.DeviceID.StartsWith' is not recognized as an internal or externa....
What am I doing wrong?
why not using powershell wmi remoting?
$cred = get-credential
Get-WmiObject Win32_PNPEntity -computerName MyRemoteComputerName - credential $cred |Where{$_.DeviceID.StartsWith("PCI\VEN_10DE") -or $_.DeviceID.StartsWith("PCI\VEN_1002")}
-credential are only needed if the actual user running powershell isn't administrator of remote machine.
Hi I needed to do some thing like this once so i wrote some code that can send any ps code to a remote computes and display the results in the ps window on your pc.
Just remember to enable powershell remoting on both pc's.
function remote-pscode ($ServerName,$UserName,$password,$PSCode)
{
$global:RemoteCode = $args[0]
Write-Host $RemoteCode
$conprops = (Get-Host).UI.RawUI
$buffsize = $conprops.BufferSize
$buffsize.Height = 800
$conprops.BufferSize= $buffsize
# Set the user name you would like to use for the connection
$global:RemoteUserName = $UserName
$global:RemoteServerName = $ServerName
# Set the password you would like to use for the connection
# Check to see if you have a file on you drive c:\cred.txt with a password to use in it,if you don't it will create one
# for you and ask you for the password you would like to use
$global:RemotePassword = convertto-securestring $password -AsPlainText -Force
$global:credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $RemoteUserName,$RemotePassword
#Create a connection to the remote computer , put a list of IPAddresses or Computer Names.
$global:session = new-PSSession -ComputerName $RemoteServerName -Credential $credentials
$ScriptBlock = $executioncontext.invokecommand.NewScriptBlock($RemoteCode)
invoke-command -Session $session -ScriptBlock $ScriptBlock
#Close the sessions that where created
$global:closesession = Get-PSSession
Remove-PSSession -Session $closesession
}
remote-pscode -ServerName "NameOfRemotePC" -UserName "UserName" -password "password" -PSCode "any powershell code you want to send to the remote pc"
Several things here: put your PS commands in a script block (or a script). Also, why don't you simply use wmic.exe ?