I am trying to renew a certificate (on my local machine) that is going to expire shortly. I know to do this manually but I can't find a way to do this using Powershell. I've looked up PKIPS and QAD but they don't seem to have any cmdlets with regard to renewing a certificate. Could anyone point me to any other library that achieves this task?
This is the function I used to renew a certificate that was generated from an Active Directory template.
function Renew-Certificate {
[CmdletBinding()]
Param([Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateNotNullOrEmpty()] [string]$Thumbprint,
[Parameter(Mandatory=$false, ValueFromPipeline=$false)] [switch]$MachineStore)
Process {
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa379399(v=vs.85).aspx
#X509CertificateEnrollmentContext
$ContextUser =0x1
$ContextMachine =0x2
$ContextAdministratorForceMachine=0x3
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa374936(v=vs.85).aspx
#EncodingType
$XCN_CRYPT_STRING_BASE64HEADER =0
$XCN_CRYPT_STRING_BASE64 =0x1
$XCN_CRYPT_STRING_BINARY =0x2
$XCN_CRYPT_STRING_BASE64REQUESTHEADER=0x3
$XCN_CRYPT_STRING_HEX =0x4
$XCN_CRYPT_STRING_HEXASCII =0x5
$XCN_CRYPT_STRING_BASE64_ANY =0x6
$XCN_CRYPT_STRING_ANY =0x7
$XCN_CRYPT_STRING_HEX_ANY =0x8
$XCN_CRYPT_STRING_BASE64X509CRLHEADER=0x9
$XCN_CRYPT_STRING_HEXADDR =0xa
$XCN_CRYPT_STRING_HEXASCIIADDR =0xb
$XCN_CRYPT_STRING_HEXRAW =0xc
$XCN_CRYPT_STRING_NOCRLF =0x40000000
$XCN_CRYPT_STRING_NOCR =0x80000000
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa379430(v=vs.85).aspx
#X509RequestInheritOptions
$InheritDefault =0x00000000
$InheritNewDefaultKey =0x00000001
$InheritNewSimilarKey =0x00000002
$InheritPrivateKey =0x00000003
$InheritPublicKey =0x00000004
$InheritKeyMask =0x0000000f
$InheritNone =0x00000010
$InheritRenewalCertificateFlag=0x00000020
$InheritTemplateFlag =0x00000040
$InheritSubjectFlag =0x00000080
$InheritExtensionsFlag =0x00000100
$InheritSubjectAltNameFlag =0x00000200
$InheritValidityPeriodFlag =0x00000400
$X509RequestInheritOptions=$InheritDefault+$InheritRenewalCertificateFlag+$InheritTemplateFlag
if ($MachineStore.IsPresent) {
$Path="Cert:\LocalMachine\My\$Thumbprint"
$Context=$ContextAdministratorForceMachine
}
else {
$Path="Cert:\CurrentUser\My\$Thumbprint"
$Context=$ContextUser
}
$Cert=Get-Item -Path $Path
$PKCS10=New-Object -ComObject X509Enrollment.CX509CertificateRequestPkcs10
$PKCS10.Silent=$true
$PKCS10.InitializeFromCertificate($Context,[System.Convert]::ToBase64String($Cert.RawData), $XCN_CRYPT_STRING_BASE64, $X509RequestInheritOptions)
$PKCS10.AlternateSignatureAlgorithm=$false
$PKCS10.SmimeCapabilities=$false
$PKCS10.SuppressDefaults=$true
$PKCS10.Encode()
#OK=$InheritTemplateFlag+$InheritNewSimilarKey
#OK=$InheritSubjectFlag+$InheritTemplateFlag+$InheritNewSimilarKey
#OK=$InheritDefault+$InheritRenewalCertificateFlag+$InheritTemplateFlag
#BAD=$InheritNewSimilarKey+$InheritRenewalCertificateFlag
#BAD=$InheritDefault+$InheritRenewalCertificateFlag (Template required)
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa377809(v=vs.85).aspx
$Enroll=New-Object -ComObject X509Enrollment.CX509Enrollment
$Enroll.InitializeFromRequest($PKCS10)
Write-Verbose "Renewing..."
$Error.Clear()
Try { $Enroll.Enroll() }
Catch {
Write-Verbose "Unable to renew"
$Errors=$Error.Clone()
$Errors | ForEach-Object { Write-Error $_.Exception.Message }
$result="0"
}
if ($Error.Count -eq 0) {
$Cert=New-Object Security.Cryptography.X509Certificates.X509Certificate2
$Cert.Import([System.Convert]::FromBase64String($Enroll.Certificate(1)))
$result=$Cert.Thumbprint
Write-Verbose "New Thumbprint is $result"
}
$result
}
}
I was looking for a Powershell solution as well, but I found that in the end, the Windows certreq command just provided more out-of-the-box operations.
Renewing a certificate with certreq then goes like:
$cert = (ls Cert:\LocalMachine\My | Where-Object { $_.Subject -eq 'CN=myserver' })[0]
&certreq #('-Enroll', '-machine', '-q', '-cert', $cert.SerialNumber, 'Renew', 'ReuseKeys')
Related
I am writing a script that checks for a registry key property created by a Bitlocker GPO, checks to see if the device is already protected and if the key is present and the device is not protected then encrypts the device.
I've tested each of these functions individually and they all work, and they all seem to work in the context of the script, outputting the right loop, however when I run the whole script at once, the only loop that DOESN'T work is the loop where the encrypting function runs.
I have the exit codes here because I am using Manage Engine to push out the script to a few remote devices around our estate and I would like to be able to see easily where the script is failing so I can deploy something else.
# FUNCTION - Function created to check existence of Active Directory GPO
Function Test-RegistryValue {
$regPath = 'HKLM:\SOFTWARE\Policies\Microsoft\FVE'
$regPropertyName = 'ActiveDirectoryBackup'
# Value of registry key ported into a script-level variable accessible from outside the function
$regValue = (Get-ItemPropertyValue -Path $regPath -Name $regPropertyName -ErrorAction ignore)
if ($regValue -eq 1)
{
$script:regExit = 0
}
# If the key exists but is not set to write back
elseif ($regValue -eq 0)
{
$script:regExit = 1
}
# If the registry property doesn't exist
else
{
$script:regExit = 5
}
}
# FUNCTION - Function tests if BitLocker protection is already on
function Test-TBBitlockerStatus {$BLinfo = Get-Bitlockervolume
if($blinfo.ProtectionStatus -eq 'On' -and $blinfo.EncryptionPercentage -eq '100')
{
return $true
}
else
{
return $false
}
}
# FUNCTION - This function is the actual encryption script
Function Enable-TBBitlocker {
Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector
$RecoveryKey = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"}
Manage-bde -protectors -adbackup $env:SystemDrive -id $RecoveryKey[0].KeyProtectorId
Enable-BitLocker -MountPoint $env:SystemDrive -UsedSpaceOnly -TpmProtector -SkipHardwareTest
}
# MAIN SCRIPT
# Running the GPO check function
Test-RegistryValue
Test-TBBitlockerStatus
$regExit
if ($regExit -eq 1)
{
Write-Host 'Key present but writeback not enabled.'
exit 51
}
# Exit if GPO not present
elseif ($regExit -eq 5)
{
Write-Host 'GPO not present.'
exit 55
}
# Test for device already being encrypted
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -eq 0))
{
Write-Host 'Writeback enabled but device already protected.'
exit 61
}
# Test for device being encrypted and writeback is not enabled.
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -ge 1))
{
Write-Host 'Device already protected but AD writeback is not enabled.'
exit 65
}
elseif ((Test-TBBitlockerStatus -eq False) -and ($regExit -eq 0))
{
Enable-TBBitlocker | Write-Output
Write-Host 'Encryption successful.'
exit 10
}
The Write-Host comments are so I can see where it's failing or succeeding. If I manually change the key value to 1 or 0, or encrypt the device and run the script the correct loop runs, however when the device is not encrypted and has the key in the registry, it does absolutely nothing. The exit code doesn't change, there's no output to the host.
I have the output included before the main if scope to see if the values are being set correctly, which they are but that final encryption function loop does not run at all.
Consider the following contrived example:
function Test-ProcessContinue {
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
Param()
for ($i = 1; $i -le 3; $i++) {
if ($PSCmdlet.ShouldProcess("$i", "Process")) {
Write-Output "Processing $i"
}
else {
Write-Verbose "No chosen"
}
}
for ($i = 1; $i -le 3; $i++) {
if ($PSCmdlet.ShouldProcess("$i", "Process")) {
Write-Output "Processing $i"
}
else {
Write-Verbose "No chosen"
}
}
$yta = $false; $nta = $false
for ($i = 1; $i -le 3; $i++) {
if ($PSCmdlet.ShouldContinue("$i", "Continue", [ref]$yta, [ref]$nta) -or $yta) {
Write-Output "Continuing with $i"
}
elseif ($nta) {
Write-Verbose "No to all chosen"
break
}
else {
Write-Verbose "No chosen"
}
}
}
...and one of its potential outputs:
PS C:\> Test-ProcessContinue -Verbose
Confirm
Are you sure you want to perform this action?
Performing the operation "Process" on target "1".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): a
Processing 1
Processing 2
Processing 3
Processing 1
Processing 2
Processing 3
Continue
1
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): a
Continuing with 1
Continuing with 2
Continuing with 3
In the case of the ShouldContinue loop (third for loop), I can see that the overload with the two by-reference boolean parameters is responsible for storing whether the end-user chose Yes to All or No to All into those two booleans.
However, in the case of the two ShouldProcess blocks (first two for loops), how is this state preserved?
In particular, in between the first two ShouldProcess blocks, how could I check if Yes to All or No to All were specified and/or what would I need to reset or clear in order to make the second ShouldProcess block ask for confirmation again?
(Favouring ShouldContinue over ShouldProcess is an option for fine-grained control, but it appears to lose the native/built-in support for [CmdletBinding(SupportsShouldProcess=$true)]
First, I'll address $PSCmdlet.ShouldContinue. This is basically a way to prompt on your own regardless of Confirm preferences.
$PSCmdlet.ShouldProcess on the other hand, doesn't always prompt. It takes into account the ConfirmImpact (which you set to High), and the $ConfirmPreference automatic variable, which defaults to High. The valid values are None, Low, Medium, and High and are meant to indicate how much of an impact a change has, so when $ConfirmPreference's value is equal to or less than a command's ConfirmImpact value, then ShouldProcess will prompt.
I know this isn't your direct question, but the background is important for answering what you should do.
The direct question: "where is the answer stored?" has a boring answer: it's stored in an internal variable in the class that defines the ShouldProcess method.
So, no, you can't get at it yourself, unfortunately.
But that brings us back to .ShouldContinue, which can take those references and store those values for you, so when you want the values, and want to be able to make decisions with them, you should use .ShouldContinue.
But, you should really use both. Because they do different things.
.ShouldProcess isn't just responsible for confirmation prompts, it's also responsible for handling -WhatIf/$WhatIfPreference; when you say your command SupportsShouldProcess you are also saying it supports -WhatIf. If you don't use .ShouldProcess, you'll get into the situation of having commands that appear to be safe but actually take action anyway.
So a pattern of something like this would cover your bases:
if ($PSCmdet.ShouldProcess('thing', 'do')) {
if ($PSCmdlet.ShouldContinue('prompt')) {
# do it
}
}
Problem with this goes back to your confirm impact and preferences. If those line up, or if the user invoked your command with -Confirm, you'll be prompting twice: once in the .ShouldProcess and then again in the .ShouldContinue.
That kind of sucks unfortunately.
I wrote a thing that seems to work around this. It's predicated first on a function that allows you to run an arbitrary scriptblock with confirmation, so that you can still run .ShouldProcess while suppressing its prompt.
Then it also tries to calculate whether a prompt is needed or not, and then selectively call .ShouldContinue. I didn't demonstrate storing or resetting the yesToAll and noToAll vars because you already know how to do that.
This is mainly to demo a pattern that could be used to adhere to standard confirmation prompt semantics, with discoverability, support for the -Confirm parameter, $ConfirmPreference, and ConfirmImpact, while maintaining support for -Verbose and -WhatIf.
function Test-Should {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param()
Begin {
$local:ShouldConfirm = $ConfirmPreference -ne [System.Management.Automation.ConfirmImpact]::None -and
$ConfirmPreference -le [System.Management.Automation.ConfirmImpact]::High # hard coded :(
function Invoke-CommandWithConfirmation {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ScriptBlock]
$ScriptBlock
)
Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock
}
}
Process {
if (Invoke-CommandWithConfirmation -ScriptBlock { $PSCmdlet.ShouldProcess('target', 'action') } -Confirm:$false ) {
if (-not $local:ShouldConfirm -or $PSCmdlet.ShouldContinue('query', 'caption')) {
'Hi' | Write-Host
'Hello' | Write-Verbose
}
}
}
}
Invocations:
Test-Should
Test-Should -Confirm
Test-Should -Confirm:$false
Test-Should -Verbose
Test-Should -Verbose -WhatIf
Test-Should -WhatIf -Confirm
Test-Should -WhatIf -Confirm:$false
And so on, with different values of $ConfirmPreference and different values for the command's ConfirmImpact.
The one thing annoying is the value I marked as hard coded: it has to match what you set as the confirm impact for that command.
It turns out it's kind of a pain in the ass to get at that value programmatically, but maybe you could work that in in some way.
This is mostly an extension of #briantist answer, but I was struggling with how to fully implement this for a while but I have gotten it working the way I want and wanted to share in case anyone else has a similar goal as me. I am hoping to turn this into a complete class that does all of this for me, but baby steps.
Function New-Function {
[CmdletBinding(
ConfirmImpact='None',
DefaultParameterSetName="Default",
HelpURI="",
SupportsPaging=$False,
SupportsShouldProcess=$True,
PositionalBinding=$True
)] Param(
[Parameter(ValueFromPipeline)]
$Items,
$Impact = 'Medium',
[Switch]$Force
)
Begin {
$PSCmdlet.GetDynamicParameters()
If (-Not $PSBoundParameters.ContainsKey('Verbose')) {
$VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference')
}
If (-not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference')
}
If (-not $PSBoundParameters.ContainsKey('WhatIf')) {
$WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference')
}
New-Variable -Name YesToAll -Value $False -Verbose:$False -Confirm:$False -WhatIf:$False
New-Variable -Name NoToAll -Value $False -Verbose:$False -Confirm:$False -WhatIf:$False
[Bool]$Local:ShouldConfirm = $Force -OR $ConfirmPreference -ne [System.Management.Automation.ConfirmImpact]::None -and $ConfirmPreference -le [System.Management.Automation.ConfirmImpact]::$Impact
$Local:ShouldProcess = ([Scriptblock]::Create(("Function ShouldProcess{{[CmdletBinding(SupportsShouldProcess, ConfirmImpact='{0}')]Param()`$PSCmdlet.ShouldProcess('{1}','{2}')}};ShouldProcess -Confirm:`$False" -f $Impact,'Target','Action')))
$Local:ShouldContinue = ([ScriptBlock]::Create(("Function ShouldContinue {{[CmdletBinding(SupportsShouldProcess, ConfirmImpact='{0}')]Param()New-Variable -Name YesToAll -Value `$PSCmdlet.GetVariableValue('YesToAll') -Verbose:`$False -Confirm:`$False -WhatIf:`$False;New-Variable -Name NoToAll -Value `$PSCmdlet.GetVariableValue('NoToAll') -Verbose:`$False -Confirm:`$False -WhatIf:`$False;`$PSCmdlet.ShouldContinue('{1}','{2}',[Ref]`$YesToAll,[Ref]`$NoToAll);Set-Variable -Name YesToAll -Value `$YesToAll -Scope 1 -Confirm:`$False -Verbose:`$False -WhatIf:`$False;Set-Variable -Name NoToAll -Value `$NoToAll -Scope 1 -Confirm:`$False -Verbose:`$False -WhatIf:`$False;}};ShouldContinue" -f $Impact,'Target','Action')))
}
Process {
ForEach ($Item in $Items) {
IF ((Invoke-Command -NoNewScope -ScriptBlock $Local:ShouldProcess) -eq $True) {
If ($Force -or $Local:ShouldConfirm -eq $False -or (Invoke-Command -NoNewScope -ScriptBlock $Local:ShouldContinue)) {
IF ($Local:Force) {
Write-Verbose -Message 'Force'
} ElseIf ($Local:YesToAll -eq $True) {
Write-Verbose -Message 'YesToAll'
} Else {
Write-Verbose -Message 'Yes'
}
Write-Host $Item
} Else {
If ($Local:NoToAll -eq $True) {
Write-Verbose -Message 'NoToAll'
} Else {
Write-Verbose -Message 'No'
}
}
}
}
}
End {
}
}
$ConfirmPreference = 'High'
'1','2','3','4','5','6','7','8','9' | New-Function -Impact Medium
I'm trying to import a PFX file into the local certificate store. However, Import-PfxCertificate just does nothing at all. No return value, no error, nothing:
I can double click on the PFX file in Explorer and import it with the same password, which works. Something about the PowerShell CmdLet isn't working. I've also tried other stores, such as Cert:\LocalMachine\My and TrustedPeople. Running it with -Verbose -Debug doesn't show anything extra. Nothing in the Application or Security event logs either. I'm also running as an admin. Ideas?
The Pfx file might have a cert chain. Treating it as a collection would be a better way of handling the certificate store. See installing cert chain for the C# this was based off;
[string] $certPath = '.\test.pfx';
[string] $certPass = 'MyPassword';
# Create a collection object and populate it using the PFX file
$collection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new();
$collection.Import($certPath, $certPass, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet);
try {
# Open the Store My/Personal
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My');
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
foreach ($cert in $collection) {
Write-Host ("Subject is: '{0}'" -f $cert.Subject )
Write-Host ("Issuer is: '{0}'" -f $cert.Issuer )
# Import the certificate into an X509Store object
# source https://support.microsoft.com/en-au/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard-net-applic
if ($cert.Thumbprint -in #($store.Certificates | % { $_.Thumbprint } )) {
Write-Warning "Certificate is already in the store"
# Force the removal of the certificate so we have no conflicts, not required if this is the first install
$store.Remove($cert)
}
# Add in the certificate
$store.Add($cert);
}
} finally {
if($store) {
# Dispose of the store once we are done
$store.Dispose()
}
}
In TFS 2015 new build system, did the functionality to automatically add build number to Global List (Build - Project Name) upon build complete removed?
Do I need to write a custom PowerShell task to accomplish this?
Note: XAML builds still add build number to Global List as it did before.
Since many features are still missing in the vNext build system, I've made a PowerShell script that do the Job.
In a near futur, I plan to update this script to support IntegratedIn field filling and to convert the script as a custom build task.
[CmdletBinding(SupportsShouldProcess=$false)]
param()
function Update-GlobalListXml
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[xml]$globalListsDoc,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$glName,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$buildNumber
)
Write-Verbose "Checking whether '$glName' exists"
$buildList = $globalListsDoc.GLOBALLISTS.GLOBALLIST | Where-Object { $_.name -eq $glName }
if ($buildList -eq $null)
{
Write-Host "GlobalList '$glName' does not exist and will be created"
$globalLists = $globalListsDoc.GLOBALLISTS
if($globalLists.OuterXml -eq $null)
{
$newDoc = [xml]"<gl:GLOBALLISTS xmlns:gl="""http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/globallists"""></gl:GLOBALLISTS>"
$globalLists = $newDoc.GLOBALLISTS
}
$globalList = $globalLists.OwnerDocument.CreateElement("GLOBALLIST")
$globalList.SetAttribute("name", $glName)
$buildList = $globalLists.AppendChild($globalList)
}
if(($buildList.LISTITEM | where-object { $_.value -eq $buildNumber }) -ne $null)
{
throw "The LISTITEM value: '$buildNumber' already exists in the GLOBALLIST: '$glName'"
}
Write-Host "Adding '$buildNumber' as a new LISTITEM in '$glName'"
$build = $buildList.OwnerDocument.CreateElement("LISTITEM")
$build.SetAttribute("value", $buildNumber)
$buildList.AppendChild($build) | out-null
return $buildList.OwnerDocument
}
function Invoke-GlobalListAPI()
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[parameter(Mandatory=$true)][Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]$wiStore,
[parameter(Mandatory=$true,ParameterSetName="Import")][switch]$import,
[parameter(Mandatory=$true,ParameterSetName="Import")][xml]$globalLists,
[parameter(ParameterSetName="Export")][switch]$export
)
try {
if($import)
{
$wiStore.ImportGlobalLists($globalLists.OuterXml) # Account must be explicitly in the Project Administrator Group
}
if($export)
{
return [xml]$wiStore.ExportGlobalLists()
}
}
catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
Write-Error "An error has occured while exporting or importing GlobalList"
throw $_
}
}
function Get-WorkItemStore()
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$tpcUri,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$agentWorker
)
# Loads client API binaries from agent folder
$clientDll = Join-Path $agentWorker "Microsoft.TeamFoundation.Client.dll"
$wiTDll = Join-Path $agentWorker "Microsoft.TeamFoundation.WorkItemTracking.Client.dll"
[System.Reflection.Assembly]::LoadFrom($clientDll) | Write-Verbose
[System.Reflection.Assembly]::LoadFrom($wiTDll) | Write-Verbose
try {
Write-Host "Connecting to $tpcUri"
$tfsTpc = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tpcUri)
return $tfsTpc.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
}
catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
Write-Error "An error has occured while retrieving WorkItemStore"
throw $_
}
}
function Get-WITDataStore64
{
[CmdletBinding(SupportsShouldProcess=$false)]
param()
if($env:VS140COMNTOOLS -eq $null)
{
throw New-Object System.InvalidOperationException "Visual Studio 2015 must be installed on the build agent" # TODO: Change it by checking agent capabilities
}
$idePath = Join-Path (Split-Path -Parent $env:VS140COMNTOOLS) "IDE"
return Get-ChildItem -Recurse -Path $idePath -Filter "Microsoft.WITDataStore64.dll" | Select-Object -First 1 -ExpandProperty FullName
}
function Update-GlobalList
{
[CmdletBinding(SupportsShouldProcess=$false)]
param()
# Get environment variables
$tpcUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
Write-Verbose "Team Project Collection Url: '$tpcUri'"
$teamProjectName = $env:SYSTEM_TEAMPROJECT
Write-Verbose "Team Project: '$teamProjectName'"
$buildNumber = $env:BUILD_BUILDNUMBER
Write-Verbose "Build Number: '$buildNumber'"
$agentHome = $env:AGENT_HOMEDIRECTORY
Write-Verbose "Agent home direrctory: '$agentHome'"
$globalListName = "Builds - $teamProjectName"
Write-Verbose "GlobalList name: '$teamProjectName'"
# Copy 'Microsoft.WITDataStore64.dll' from Visual Studio directory to AgentBin directory if it does not exist
$agentWorker = Join-Path $agentHome "agent\Worker"
$targetPath = Join-Path $agentWorker "Microsoft.WITDataStore64.dll" # Only compatible with x64 process #TODO use constant instead
if(-not (Test-Path $targetPath))
{
$wITDataStore64FilePath = Get-WITDataStore64
Write-Host "Copying $wITDataStore64FilePath to $targetPath"
Copy-Item $wITDataStore64FilePath $targetPath | Write-Verbose
}
$wiStore = Get-WorkItemStore -tpcUri $tpcUri -agentWorker $agentWorker
# Retrive GLOBALLISTS
$xmlDoc = Invoke-GlobalListAPI -export -wiStore $wiStore
$gls2 = Update-GlobalListXml -globalListsDoc $xmlDoc -glName $globalListName -buildNumber $buildNumber
Invoke-GlobalListAPI -import -globalLists $gls2 -wiStore $wiStore
}
Update-GlobalList
Here is the link of the Github repo, feedbacks are welcome => https://github.com/GregoryOtt/UpdateWiBuildNum/blob/master/Update-GlobalList.ps1
[disclaimer - I work on the new build system]
That global list on the workitem is a mechanism that dated back to the original release of TFS. It's one that sort of worked in that day and age (days of nightly builds, pre-CI and CD agility). It's starts to fall apart and doesn't show as proper relationships in TFS. I worked on WIT at that time and we needed a queryable mechanism and that's what we had (blame me :)
So, when we started a new build system, we didn't want to rebuild things and repeat the same mistakes. We're trying to take an agile, incremental approach to a better build system.
In the next sprint (88), we are starting work on proper links between builds and workitems and the WIT team is also doing work to make them more first class. The first thing you'll see is a link on the WIT form and that should hopefully make QU1 as well (at least parts of it).
We realize this does leave a few gaps but we are working to close them (gated and label sources being two others) and hopefully in a better way for a better long term product.
As far as a workaround goes, it should be possible to automate via powershell and our clients but we don't have anything canned for others to use.
I'm trying to find a way to grant permissions for private key from powershell script. Certificate is stored in CNG. All ideas are welcome.
The answer above is technically correct however it did not help me when I was looking for the same thing because it fails to mention that you need to use assemblies loaded from the CLRSecurity project on codeplex https://clrsecurity.codeplex.com/.
Here is an extract of how I achieved the same thing including loading the CLR Security assembly that you need to use Security.Cryptography.dll. There are a couple of function declarations that are needed first. I have these included in modules however you can use them as you wish.
Function Load-Assembly()
{
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)][string][ValidateScript({Test-Path $_})] $DirectoryPath,
[Parameter(Mandatory)][string][ValidateNotNullOrEmpty()] $Name
)
$assemblyFileNameFullPath = Join-Path -Path $DirectoryPath -ChildPath $Name
If (Test-Path -Path $assemblyFileNameFullPath -PathType Leaf)
{
Write-Verbose "Loading .NET assembly from path ""$assemblyFileNameFullPath"""
#Load the assembly using the bytes as this gets around security restrictions that stop certain assemblies from loading from external sources
$assemblyBytes = [System.IO.File]::ReadAllBytes($assemblyFileNameFullPath)
$assemblyLoaded = [System.Reflection.Assembly]::Load($assemblyBytes);
if ($assemblyLoaded -ne $null)
{
return $assemblyLoaded
}
else
{
Throw "Cannot load .NET assembly ""$Name"" from directory ""$DirectoryPath"""
}
}
else
{
Write-Error "Cannot find required .NET assembly at path ""$assemblyFileNameFullPath"""
}
}
Function Get-PrivateKeyContainerPath()
{
[CmdletBinding(PositionalBinding=$false)]
Param(
[Parameter(Mandatory=$True)][string][ValidateNotNullOrEmpty()] $Name,
[Parameter(Mandatory=$True)][boolean] $IsCNG
)
If ($IsCNG)
{
$searchDirectories = #("Microsoft\Crypto\Keys","Microsoft\Crypto\SystemKeys")
}
else
{
$searchDirectories = #("Microsoft\Crypto\RSA\MachineKeys","Microsoft\Crypto\RSA\S-1-5-18","Microsoft\Crypto\RSA\S-1-5-19","Crypto\DSS\S-1-5-20")
}
foreach ($searchDirectory in $searchDirectories)
{
$machineKeyDirectory = Join-Path -Path $([Environment]::GetFolderPath("CommonApplicationData")) -ChildPath $searchDirectory
$privateKeyFile = Get-ChildItem -Path $machineKeyDirectory -Filter $Name -Recurse
if ($privateKeyFile -ne $null)
{
return $privateKeyFile.FullName
}
}
Throw "Cannot find private key file path for key container ""$Name"""
}
#Extracted code of how to obtain the private key file path (taken from a function)
#Requires an x509Certificate2 object in variable $Certificate and string variable $CertificateStore that contains the name of the certificate store
#Need to use the Security.Cryptography assembly
$assembly = Load-Assembly -DirectoryPath $PSScriptRoot -Name Security.Cryptography.dll
#Uses the extension methods in Security.Cryptography assembly from (https://clrsecurity.codeplex.com/)
If ([Security.Cryptography.X509Certificates.X509CertificateExtensionMethods]::HasCngKey($Certificate))
{
Write-Verbose "Private key CSP is CNG"
$privateKey = [Security.Cryptography.X509Certificates.X509Certificate2ExtensionMethods]::GetCngPrivateKey($Certificate)
$keyContainerName = $privateKey.UniqueName
$privateKeyPath = Get-PrivateKeyContainerPath -Name $keyContainerName -IsCNG $true
}
elseif ($Certificate.PrivateKey -ne $null)
{
Write-Verbose "Private key CSP is legacy"
$privateKey = $Certificate.PrivateKey
$keyContainerName = $Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$privateKeyPath = Get-PrivateKeyContainerPath -Name $keyContainerName -IsCNG $false
}
else
{
Throw "Certificate ""$($Certificate.GetNameInfo("SimpleName",$false))"" in store ""$CertificateStore"" does not have a private key, or that key is inaccessible, therefore permission cannot be granted"
}
Sorry if this seems like a repeat from above, as I said it does use the same technique but hopefully others may find this more useful since it explains how to use the methods in the CLR Security project including how to load the assembly.
Cmdlet code for getting private key filename.
[Cmdlet("Get", "PrivateKeyName")]
public class GetKeyNameCmdlet : Cmdlet
{
[Parameter(Position = 0, Mandatory = false)]
public X509Certificate2 Cert;
protected override void ProcessRecord()
{
WriteObject(GetUniqueKeyName(Cert));
}
private static string GetUniqueKeyName(X509Certificate2 cert)
{
if (cert == null)
throw new ArgumentNullException("cert");
var cngPrivateKey = cert.GetCngPrivateKey();
if (cngPrivateKey != null)
return cngPrivateKey.UniqueName;
var rsaPrivateKey = cert.PrivateKey as RSACryptoServiceProvider;
if (rsaPrivateKey != null)
return rsaPrivateKey.CspKeyContainerInfo.UniqueKeyContainerName;
throw new Exception("cert");
}
}
using cmdlet. CngCrypt.dll - dll with cmdlet code.
Import-Module .\CngCrypt.dll
$local:certificateRootPath = join-path $env:ALLUSERSPROFILE '\Microsoft\Crypto\RSA\MachineKeys\'
$WorkingCert = Get-ChildItem CERT:\LocalMachine\My |where {$_.Subject -match 'Test'}| sort
Get-PrivateKeyName ($WorkingCert)
If you have certificate already installed on machine/server and just looking for how to give permission to specific user using powershell.
Here is the answer
How to Grant permission to user on Certificate private key using powershell?