Skip to content

How can I automate certificate-import to group-policy

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:

  1. Import the certificate to “CERT:\LocalMachine\Root”
  2. Move the registry-key “HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT” to “HKLM:\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT”
  3. 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.
}
Advertisements

Building a Sybase-NuGet-package with NuGet Package Explorer

This package will handle the unmanaged Sybase-assemblies that can not be referenced in your project but have to reside in the “bin”-folder. I will call the package Company.Sybase.1.0.0.nupkg.

Package structure

  • build
    • net
      • Company.Sybase.targets
  • lib
    • net20
      • Sybase.AdoNet2.AseClient.dll
      • Sybase.Data.AseClient.dll
    • net40
      • Sybase.AdoNet4.AseClient.dll
      • Sybase.Data.AseClient.dll
    • Unmanaged
      • sybdrvado115a.dll

Company.Sybase.nuspec

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
	<metadata>
		<authors>Company</authors>
		<description>Sybase assemblies.</description>
		<id>Company.Sybase</id>
		<owners>Company</owners>
		<references>
			<reference file="Sybase.AdoNet2.AseClient.dll" />
			<reference file="Sybase.AdoNet4.AseClient.dll" />
			<reference file="Sybase.Data.AseClient.dll" />
		</references>
		<requireLicenseAcceptance>false</requireLicenseAcceptance>
		<title>Company.Sybase</title>
		<version>1.0.0</version>
	</metadata>
</package>

Company.Sybase.targets

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<PropertyGroup>
		<BuildDependsOn>
			ResolveSybaseReferenceDependencies;
			ResolveSybaseDependenciesOnBuild;
			$(BuildDependsOn);
		</BuildDependsOn>
		<CleanDependsOn>
			ResolveOutputPath;
			ResolveSybaseReferenceDependencies;
			ResolveSybaseDependenciesOnClean;
			$(CleanDependsOn);
		</CleanDependsOn>
		<CopyAllFilesToSingleFolderForPackageDependsOn>
			ResolveOutputPath;
			ResolveSybaseReferenceDependencies;
			ResolveSybaseDependenciesOnPublish;
			$(CopyAllFilesToSingleFolderForPackageDependsOn);
		</CopyAllFilesToSingleFolderForPackageDependsOn>
	</PropertyGroup>
	<Target Name="ResolveOutputPath">
		<PropertyGroup>
			<_ResolvedOutputPath>$(OutputPath)</_ResolvedOutputPath>
			<_ResolvedOutputPath Condition="!$(_ResolvedOutputPath.EndsWith('\'))">$(_ResolvedOutputPath)\</_ResolvedOutputPath>
		</PropertyGroup>
	</Target>
	<Target Name="ResolveSybaseDependenciesOnBuild">
		<Copy
			DestinationFolder="$(OutputPath)"
			SourceFiles="@(_SybaseReferenceDependency)"
		/>
	</Target>
	<Target Name="ResolveSybaseDependenciesOnClean">
		<Delete
			Files="$(_ResolvedOutputPath)%(_SybaseReference.Filename)%(_SybaseReference.Extension)"
		/>
		<Delete
			Files="$(_ResolvedOutputPath)%(_SybaseReferenceDependency.Filename)%(_SybaseReferenceDependency.Extension)"
		/>
	</Target>
	<Target Name="ResolveSybaseDependenciesOnPublish">
		<ItemGroup>
			<FilesForPackagingFromProject Include="@(_SybaseReferenceDependency)">
				<DestinationRelativePath>$(_ResolvedOutputPath)%(Filename)%(Extension)</DestinationRelativePath>
			</FilesForPackagingFromProject>
		</ItemGroup>
	</Target>
	<Target Name="ResolveSybaseReferenceDependencies">
		<ItemGroup>
			<_SybaseDataAseClientReferenceInternal Include="@(Reference)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('Sybase.Data.AseClient'))" />
		</ItemGroup>
		<PropertyGroup>
			<_SybaseDataAseClientReferenceInternalHintPath>%(_SybaseDataAseClientReferenceInternal.HintPath)</_SybaseDataAseClientReferenceInternalHintPath>
		</PropertyGroup>
		<PropertyGroup>
			<_SybasePackageLibraryPath>$(_SybaseDataAseClientReferenceInternalHintPath.Substring(0, $([MSBuild]::Add($(_SybaseDataAseClientReferenceInternalHintPath.IndexOf('\lib\')), 5))))</_SybasePackageLibraryPath>
		</PropertyGroup>
		<ItemGroup>
			<_SybaseReference Include="%(Reference.HintPath)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('Sybase.'))" />
		</ItemGroup>
		<ItemGroup>
			<_SybaseReferenceDependency Include="$(_SybasePackageLibraryPath)Unmanaged\*.dll" />
		</ItemGroup>
	</Target>
</Project>

Trigger a reboot with DSC Script Resource

Script RebootWhenSet
{
	GetScript = {
		# TestScript = $true
		return @{ Result = "Some value" };
 
        	# TestScript = $false
		# return @{ Result = $null };
	}
	SetScript = {
        	# Set things
        	# ...
 
		$global:DSCMachineStatus = 1;
	}
	TestScript = {
		$result = (Invoke-Expression -Command $GetScript)["Result"];
 
		if($result)
		{
			return $true;
		}
 
		return $false;
	}
}

Get the source code of the PowerShell cmdlets

Example:

$commandName = "Write-Host";
 
(Get-Command $commandName).DLL;
(Get-Command $commandName).ImplementingType;

Then you can use a decompiler to look at the code, eg. Reflector, ILSpy etc.

Running and testing DSC configurations “manually”

I wanted to run and test my DSC configurations, for my Azure-Resource-Manager-Templates, “manually”.

Change and test the scripts with Windows PowerShell ISE. I have tested them on a Windows Server 2016 Datacenter machine in Azure.

First, PowerShell have to find your resource-modules:

  • Alternative 1: Copy all your modules to “C:\Program Files\WindowsPowerShell\Modules” or “C:\Program Files (x86)\WindowsPowerShell\Modules”.
  • Alternative 2: Add your resource-module path to $env:PSModulePath.

Alternative 2

$yourModulePath = "C:\DSC\YourModules";
 
# Backup the original module-paths variable.
$originalModulePath = $env:PSModulePath;
 
# Add your path.
$env:PSModulePath = $env:PSModulePath + ";" + $yourModulePath;
 
# Run/test your DSC-configurations.
# ...
 
# Reset to the original module-paths.
$env:PSModulePath = $originalModulePath;

1 Compile your configurations

Before you can test your configuration, you have to compile it into a MOF document.

If you have [PSCredential]-parameters in your configuration you have to allow it. See the second example.

1.1 Examples

1.1.1 My-DSC-Configuration.ps1

configuration MyDscConfiguration
{
	param
	(
		[Parameter(Mandatory)]
		[string]$myParameter
	)
	
	Import-DscResource -Module YourModule, PSDesiredStateConfiguration;
	
	node localhost
	{
		LocalConfigurationManager
		{
			RebootNodeIfNeeded = $true;
		}
	
		YourModuleResource YourModuleResource
		{
			MyProperty = $myParameter;
		}
	}
}
 
# Above is your configuration (paste it from your configuration-file). Below is for compiling.
 
MyDscConfiguration -MyParameter "Test" -OutputPath $PSScriptRoot -Verbose;

1.1.2 My-DSC-Configuration-With-Credentials.ps1

configuration MyDscConfigurationWithCredentials
{
	param
	(
		[Parameter(Mandatory)]
		[PSCredential]$credentials
	)
	
	Import-DscResource -Module YourModule, PSDesiredStateConfiguration;
	
	node localhost
	{
		LocalConfigurationManager
		{
			RebootNodeIfNeeded = $true;
		}
	
		YourModuleResource YourModuleResource
		{
			Credentials = $credentials;
		}
	}
}
 
# Above is your configuration (paste it from your configuration-file). Below is for compiling.
 
# Configure to allow domain-credentials.
$configuration = @{
	AllNodes = @(
		@{
			NodeName = "localhost";
			PSDscAllowDomainUser = $true;
			PSDscAllowPlainTextPassword = $true;
		}
	)
}
 
$credentials = Get-Credential -Message "Password please" -UserName "yourdomain.net\User-name";
 
MyDscConfigurationWithCredentials -ConfigurationData $configuration -Credentials $credentials -OutputPath $PSScriptRoot -Verbose;

2 Run/test – Run.ps1

# The $PSScriptRoot variable is where this script is located.
# If you use this variable this script must be located in the same directory as your compiled-configuration-mof files.
# Otherwise change the -Path parameter to where your compiled-configuration-mof files are.
 
# Get the configuration.
# https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/get-dscconfiguration/
Get-DscConfiguration;
 
# Test the configuration.
# https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/test-dscconfiguration/
Test-DscConfiguration -Path $PSScriptRoot;
 
# Run the configuration.
# https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/start-dscconfiguration/
Start-DscConfiguration -Path $PSScriptRoot -Wait -Verbose;
# If you get an error: "A configuration is pending."
# You can use the following command:
# Start-DscConfiguration -Path $PSScriptRoot -Force -Wait -Verbose;

.NET Framework project with Visual Studio 2017 csproj

In Visual Studio 2017 you have the following project-templates (among others):

  • Class Library (.NET Core)
  • Class Library (.NET Framework)
  • Class Library (.NET Standard)

If you create a .NET Core or .NET Standard class-library you get the new “skinny” project-file-format. If you create a .NET Framework class-library you get the “old” format. But it is easy to change a clean .NET Core or .NET Standard project to a .NET Framework project. Just change the TargetFramework-tag in the project file.

.NET Core

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
</Project>

.NET Standard

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.6</TargetFramework>
  </PropertyGroup>
</Project>

.NET Framework

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
  </PropertyGroup>
</Project>

A list of the target-frameworks is found here: Target frameworks

Other csproj-links

Where is the taskbar in Windows?

The taskbar in Windows is here:

  • C:\Users\USERNAME\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

Found the answer here:

Taskbar in Swedish = Aktivitetsfältet

You can translate windows-terms here: