Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exchange script adjustments #702

Merged
merged 6 commits into from
Feb 22, 2018
Merged

Exchange script adjustments #702

merged 6 commits into from
Feb 22, 2018

Conversation

LBegnaud
Copy link
Collaborator

Supports the ability to import cert from filesystem, add option to remove old exchange certificates, and cleans up logic

Might be best to commit and squash.

Copy link

@johlju johlju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Just giving my thoughts.

[Parameter(Position=1,Mandatory=$true)]
[string]$ExchangeServices
[string]$ExchangeServices, # comma-separated list of exchange services to import for
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make this a ValidateSet() with the possible values 'IMAP', 'POP', 'UM', 'IIS', 'SMTP', 'Federation', 'UMCallRouter'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that might be a bit out of scope as it would require updating with specific knowledge on Exchange. Keep in mind this script should never be run interactively.

[string]$ExchangeServices
[string]$ExchangeServices, # comma-separated list of exchange services to import for
[Parameter(Position=2,Mandatory=$false)]
[bool]$LeaveOldExchangeCerts = 1, # 0 for false, 1 for true. If set to 1, will skip removal of old exchange certs
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use $true and $false instead of 0 and 1?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$true and $false are a pain to pass through from cmd. passing 0 and 1 are dead simple.

Since it's a bool, powershell will interpret all those the same if you really want to.

## Documentation referenced from https://technet.microsoft.com/en-us/library/aa997231(v=exchg.160).aspx

# Should work with exchange 2007 and higher
Get-PSSnapin -registered | ? {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue
Get-PSSnapin -registered | Where-Object {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue

#$OldThumbprint = (Get-Item -Path RDS:\GatewayServer\SSLCertificate\Thumbprint).CurrentValue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this comment.

## Documentation referenced from https://technet.microsoft.com/en-us/library/aa997231(v=exchg.160).aspx

# Should work with exchange 2007 and higher
Get-PSSnapin -registered | ? {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue
Get-PSSnapin -registered | Where-Object {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue

#$OldThumbprint = (Get-Item -Path RDS:\GatewayServer\SSLCertificate\Thumbprint).CurrentValue
$CertInStore = Get-ChildItem -Path Cert:\LocalMachine -Recurse | Where-Object {$_.thumbprint -eq $NewCertThumbprint} | Sort-Object -Descending | Select-Object -f 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Sort-Object -Descending by default sort on the correct date field, so that the newest are on top?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure why i kept using sort, since I'm not specifying the field to sort on and, more importantly, because we are filtering by thumbprint so we will never get certs from different days.

@@ -1,56 +1,95 @@
param(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add comment-based help with a .SYNOPSIS, and an .EXAMPLE in the comment-based help how to call this using Let's encrypt? If there are more than one option to call we should add more than one .EXAMPLE.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little torn on whether to put it there or just in the wiki. I've never done that so I can see how difficult it is. probably cleaner than the way i have remarks currently

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better? That's pretty cool.
image

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.


$cert = $SourceStore.Certificates | Where-Object {$_.thumbprint -eq $CertInStore.Thumbprint}
# Now try to find it again...
$CertInStore = Get-ChildItem -Path Cert:\LocalMachine -Recurse | Where-Object {$_.thumbprint -eq $NewCertThumbprint} | Sort-Object -Descending | Select-Object -f 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need to do this, Import-ExchangeCertificate returns the certificate if you add $CertInStore = Import-ExchangeCertificate @importExchangeCertificateParameters -ErrorAction Stop or throws an error (stops) if it can't find the certificate.

If you keep this row, the same comment about -Descending as above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems cleaner to do this so we are sure to get the right type of object. I don't have an exchange install to know if the passthrough from import-exchangecertificate returns the same type of object as get-childitem. I like to play it safe in that regard.

# Make sure variable is defined
$null = Get-ChildItem $CertInStore.PSPath -ErrorAction Stop
# Cert must exist in the personal store of machine to bind to exchange services
if($CertInStore.PSPath -notlike "*LocalMachine\My\*"){
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Are we using this code path as a fail safe if the Import-ExchangeCertificate did not add the certificate to the LocalMachine\My store? Is there a scenario when Import-ExchangeCertificate does not import the certificate to the LocalMachine\My store?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, keep in mind the script's original intent was not to be used with centralssl (the fqdn and storepath aren't required parameters)


if(-not $LeaveOldExchangeCerts){
"Old Exchange certificates being cleaned up"
Get-ExchangeCertificate -DomainName $CertInStore.Subject.split("=")[1] | Where-Object -FilterScript {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$CertInStore.Subject.split("=")[1]

Could we use $TargetHost instead of this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TargetHost isn't guaranteed to be passed. According to everything I'm reading in these issue threads, the primary CN of the cert should be the same as TargetHost

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Didn't thought about that. Looks good to me.


}catch{
"Cert thumbprint was not set successfully"
"Error: $($Error[0])"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Error: $($Error[0])"

Could we use throw $_ instead, to throw a real error? Or after this row maybe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a question for @WouterTinus . I don't see the difference really. Either way it'll be logged.

Disclaimer: I'm not a proper developer, so I'm not really well-versed error handling. Outputting them always works fine enough for my needs.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Your way works as well if outputting is the only requirement and not stopping execution telling the user something failed with red error text. :)
Looks good to me.

@LBegnaud
Copy link
Collaborator Author

Updated per recommendations from @johlju

Copy link

@johlju johlju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome job adding comment based help! Added a few more suggestive comments around the code.

@@ -1,56 +1,95 @@
param(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

$CertInStore = Get-ChildItem -Path Cert:\LocalMachine -Recurse | Where-Object {$_.thumbprint -eq $NewCertThumbprint} | Select-Object -f 1
try{
if($CertInStore.PSPath -notlike "*LocalMachine\My\*"){
"Cert thumbprint not found in the cert store... which means we should load it. This means TargetHost and StorePath must be specified"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is not certain that $TargetHost and $StorePath is passed, then if they are not, we should not go down this code path since Import-ExchangeCertificate will fail.
So we should add something like

if ($TargetHost -and $StorePath)
{ 
    ...
    $null = Import-ExchangeCertificate @importExchangeCertificateParameters -ErrorAction Stop
    ...
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'll fail into the catch block. I see no functional difference since both would stop the script from continuing.

Get-PSSnapin -registered | ? {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue
Get-PSSnapin -registered | Where-Object {$_.Name -match "Microsoft.Exchange.Management.PowerShell" -and ($_.Name -match "Admin" -or $_.Name -match "E2010" -or $_.Name -match "SnapIn")} | Add-PSSnapin -ErrorAction SilentlyContinue

$CertInStore = Get-ChildItem -Path Cert:\LocalMachine -Recurse | Where-Object {$_.thumbprint -eq $NewCertThumbprint} | Select-Object -f 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect the certificate to me in LocalMachine\My. Why don't we just look for it there?

$CertInStore = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.thumbprint -eq $NewCertThumbprint}

try
{
    if (-not $CertInStore)
    {
        ...
        $null = Import-ExchangeCertificate @importExchangeCertificateParameters -ErrorAction Stop
        ...
    }
}


$cert = $SourceStore.Certificates | Where-Object {$_.thumbprint -eq $CertInStore.Thumbprint}
# Make sure variable is defined
$null = Get-ChildItem $CertInStore.PSPath -ErrorAction Stop
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do the change above, and below, then this row is not needed.


# Cert must exist in the personal store of machine to bind to exchange services
if($CertInStore.PSPath -notlike "*LocalMachine\My\*"){
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the same check as above here.

try
{
    if (-not $CertInStore)
    {
        ...
        $SourceStoreScope = 'LocalMachine'
        $SourceStoreName = 'My'
        ...
    }
}

PrivateKeyExportable = $true
}

$null = Import-ExchangeCertificate @importExchangeCertificateParameters -ErrorAction Stop
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just do $CertInStore = Import-ExchangeCertificate ... here and skip the row below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to the docs, there are no outputs of this cmdlet

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the method of getting the cert after the import is a bit of a check to make sure $NewCertThumbprint is set properly.


if(-not $LeaveOldExchangeCerts){
"Old Exchange certificates being cleaned up"
Get-ExchangeCertificate -DomainName $CertInStore.Subject.split("=")[1] | Where-Object -FilterScript {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Didn't thought about that. Looks good to me.


}catch{
"Cert thumbprint was not set successfully"
"Error: $($Error[0])"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Your way works as well if outputting is the only requirement and not stopping execution telling the user something failed with red error text. :)
Looks good to me.

@WouterTinus
Copy link
Member

@LBegnaud - How are you feeling about this? My gut says that the script is much better already than the once that's currently being distributed in our releases. Would fixing the parameter type issue at a later date cause problems for people upgrading? I guess what I mean to say is don't let perfect get in the way of good :).

@nemchik
Copy link
Contributor

nemchik commented Feb 22, 2018

I have reviewed the existing script and the proposed script and I'm confident that what they are both trying to do is going to work fine, but it does seem like the new changes to the proposed script might need a little touching up before merging.

@LBegnaud
Copy link
Collaborator Author

I honestly had forgotten about this. I think that it's fine in its current state. I'm going to make a quick fix to the parameter to be an int that gets passed a 0 or 1 as I don't have time to mess with it too much right now. I'll push a commit then you're good to merge.

@LBegnaud
Copy link
Collaborator Author

K I think it's ready. Thanks for the reminder

@WouterTinus WouterTinus merged commit 44ec90e into win-acme:master Feb 22, 2018
@WouterTinus
Copy link
Member

No problem, thanks for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants