LetsEncrypt with ServiceDeskPlus

I thought I had typed this up at one point, but apparently I can’t find it on the blog. Here’s the steps I take to install and renew SSL certificates for ServiceDeskPlus MSP using LetsEncrypt. I’ve adapted these notes for public consumption.

My environment is a Windows Server that hosts the SDP MSP instance.

On the SDP MSP host, generate a certificate. I am using Certbot Win32 on the Windows host.

certbot certonly --standalone -d servicedesk.example.com

The generated certificate files will be in: C:\certbot\archive\servicedesk.example.com

Create PFX for import into ServiceDeskPlus MSP

Copy the privkeyX.pem, certX.pem, chainX.pem from the above directory to a machine that has OpenSSL tools installed (most Linux).

NOTE: Where X is the number in iteration; at the time of this documentation it is 2.

Issue the following command to generate a PFX, specifying the passphrase to use.

  • Linux with OpenSSL package installed:
openssl pkcs12 -export -out certificate.pfx -inkey privkey.pem -in cert.pem -certfile chain.pem
  • Windows host with OpenSSL 32/64 installed:
"\openssl\bin\openssl.exe" pkcs12 -export -out certificate.pfx -inkey privkey2.pem -in cert2.pem -certfile chain2.pem -password pass:PASSPHRASEHERE

Prepare the Certificate for Import into SDP MSP

Copy the certificate.pfx file to your computer

Update ServiceDeskPlus MSP

  1. Log into ServiceDeskPlus MSP web. Go to Admin. Search for SSL (it will load the Import SSL Certificate).
  2. Select the generated certificate.pfx on the webpage, providing the passphrase set above.
  3. Once imported successfully, must restart ServiceDeskPlus MSP. Can be done on the Windows server in Services.

Error Importing?

NOTE: The Java version of SDP MSP appears to not support the algorithm used to create the certificate when I try to import via the SDP MSP web admin interface. I get the following error in the log dump:

Invalid KeySpec: unknown PRF algorithm 1.2.840.113549.2.9

So work around is to Import the certificate.pfx that was just created into the computer’s Personal store then export it using TripleDES-SHA1 encryption.

Import:

  1. Double click the certificate.pfx file which launches an import certificate wizard.
  2. Click the Local Machine and click Next.
  3. Check the file location of the certificate and press Next.
  4. Enter the Password and enable “Mark this key as exportable” and press Next.
  5. Choose the appropriate certificate store, in this example the personal certificate store is chosen. After that, press Next.
  6. Click Finish to complete the import.

Export:

  1. Launch Microsoft Management Console. Press Win+R, type in mmc and press OK.
  2. Click File and select the Add/Remove Snap-in option.
  3. Click on Certificates in the list of Available snap-ins and then, on the Add button.
  4. Select Computer account and click Next.
  5. Choose Local Computer and click on the Finish button.
  6. Click OK to add the certificate snap-in and get back to console.
  7. Expand the Personal store in the left-side menu, and choose Certificates. Right-click on the certificate you want to export All Tasks > Export.
  8. This will run the Certificate Export Wizard.
  9. If the radio button ‘Yes, export the private key’ is grayed out, it means that either the private key was not marked as exportable during the certificate request generation, or that you do not have the corresponding private key on the machine you are using.
    • Note: if you used IIS Manager certificate request wizard to generate the CSR code, the private key will be marked as exportable by default. In this case, you will not be able to create a PFX file, only export the certificate without the private key. To have the opportunity to export the certificate to another machine, you will need to create a new CSR code marking the private key as exportable and perform a certificate reissue. Otherwise, you can generate a new CSR code for the same common name on the new machine and import the certificate to it after the reissue is completed.
  10. If you can export the private key, proceed to the next stage. The window Export File Format will have the format Personal Information Exchange – PKCS #12 (.PFX) selected. Please check Include all certificates in the certification path if possible to have the certificate exported with the chain of intermediate CA certificates into a .pfx file. Then click Next.
    • Note: do not choose ‘Delete the private key if the export is successful’.
  11. Type and confirm password on the next window and click Next. Make sure you remember the password; it will be used later during the import of a .pfx file to a new server.
    • Note: The encryption method used for the password has to be ‘TripleDES’. Also, the password should not contain the symbol ‘&’
  12. In the File to Export window select the name and location of the .pfx file to which the certificate and private key will be exported.
  13. Click Finish to complete the export wizard. The certificate has been successfully imported.

Update ServiceDeskPlus MSP:

  1. Log into ServiceDeskPlus MSP web. Go to Admin. Search for SSL (it will load the Import SSL Certificate).
  2. Select the generated certificate.pfx on the webpage, providing the passphrase set above.
  3. Once imported successfully, must restart ServiceDeskPlus MSP. Can be done on the Windows server in Services.

Other Thoughts

I think I can take advantage of certutil on Windows to import/export the certificate to avoid a lot of manual work. I know I can import the certificate easily, just haven’t tinkered with exporting it to meet the requirements of SDP MSP.

Something like this will import: certutil -f -p PASSPHRASE -importpfx certificate.pfx

Analyze RDP Disconnection Logs using PowerShell

The PowerShell script is designed to extract information about Remote Desktop Protocol (RDP) local session manager events from the Windows event logs on a RDS host and save it to a CSV file.

Script: Get events with EventID 40 from Microsoft-Windows-TerminalServices-LocalSessionManager/Operational Event Log

$RDPAuths = Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'-FilterXPath '<QueryList><Query Id="0"><Select>*[System[EventID=40]]</Select></Query></QueryList>'

[xml[]]$xml = $RDPAuths | ForEach-Object { $_.ToXml() }

$EventData = $xml.Event | ForEach-Object {
    [PSCustomObject]@{
        TimeCreated = (Get-Date $_.System.TimeCreated.SystemTime -Format 'yyyy-MM-dd hh:mm:ss K')
        Session     = $_.UserData.EventXML.Session
        Reason      = $_.UserData.EventXML.Reason
        EventID     = $_.System.EventID
        User        = $_.UserData.EventXML.User
        SessionID   = $_.UserData.EventXML.SessionID
        Address     = $_.UserData.EventXML.Address
    }
}

$EventData | Export-Csv -Path c:\rdlog-LSM-Operational.csv -Encoding ASCII

Here is an explanation of how the code works.

  1. First, the script uses the Get-WinEvent cmdlet to retrieve events from the ‘Microsoft-Windows-TerminalServices-LocalSessionManager/Operational’ log. This log contains information about RDP disconnections. The events are stored in the $RDPAuths variable.
  2. The $RDPAuths variable is then piped to the ForEach-Object cmdlet to convert each event to XML format using the ToXml() method. The resulting array of XML objects is stored in the $xml variable.
  3. Next, the script uses the ForEach-Object cmdlet again to iterate over the $xml.Event array. For each event, a new custom object is created using the [PSCustomObject]@{} syntax. This custom object contains the following properties: TimeCreatedSessionReasonEventIDUserSessionID, and Address. These properties are extracted from the event XML data using dot notation.
  4. Finally, the custom objects are piped to the Export-Csv cmdlet, which saves the objects as a CSV file at the specified path (c:\rdlog-LSM-Operational.csv) with ASCII encoding.

This PowerShell script is useful for extracting and analyzing RDP local session manager events, such as monitoring for disconnect reasons. The resulting CSV file provides a simple, convenient way to view and analyze the RDP events.

RDS Session Host Server Disconnect Codes

RDS server client disconnect codeDisconnect reason
0x00000001The disconnection was initiated by an administrative tool on the server in another session.
0x00000002The disconnection was due to a forced logoff initiated by an administrative tool on the server in another session.
0x00000003The idle session limit timer on the server has elapsed.
0x00000004The active session limit timer on the server has elapsed.
0x00000005Another user connected to the server, forcing the disconnection of the current connection.
0x00000006The server ran out of available memory resources.
0x00000007The server denied the connection.
0x00000009The user cannot connect to the server due to insufficient access privileges.
0x0000000A (10)The server does not accept saved user credentials and requires that the user enter their credentials for each connection.
0x0000000B (11)The disconnection was initiated by the user disconnecting his or her session on the server or by an administrative tool on the server.
0x0000000C (12)The disconnection was initiated by the user logging off his or her session on the server.

Extended Disconnect Reason Codes

Reference: https://learn.microsoft.com/en-us/windows/win32/termserv/extendeddisconnectreasoncode

Extended ReasonCode
NoInfo0
APIInitiatedDisconnect1
APIInitiatedLogoff2
ServerIdleTimeout3
ServerLogonTimeout4
ReplacedByOtherConnection5
OutOfMemory6
ServerDeniedConnection7
ServerDeniedConnectionFips8
ServerInsufficientPrivileges9
ServerFreshCredsRequired10
RpcInitiatedDisconnectByUser11
LogoffByUser2
LicenseInternal256
LicenseNoLicenseServer257
LicenseNoLicense258
LicenseErrClientMsg259
LicenseHwidDoesntMatchLicense260
LicenseErrClientLicense261
LicenseCantFinishProtocol262
LicenseClientEndedProtocol263
LicenseErrClientEncryption264
LicenseCantUpgradeLicense265
LicenseNoRemoteConnections266
LicenseCreatingLicStoreAccDenied267
RdpEncInvalidCredentials768
ProtocolRangeStart4096
ProtocolRangeEnd32767

Extracting unique words from all my blog post titles

Had an idea to extract all the unique words from my blog post titles and sort and rank them by frequency. I used MySQL, sed, tr, grep, cat and a little bash script hacked together to do this.

Here’s the top 10 unique words in my blog post titles.

OccurrencesWord
150Windows
46Server
32Cisco
26Command
25Microsoft
22SQL
20Explorer
19Linux
18Internet
18Error

Here’s how I got to this…

SQL Query

select id,post_title from wp_posts where post_type='post' and post_status='publish'

Bash Script

The script splits each word into a new line and also removes any non-alphanumeric characters sh split.sh > single-words.txt

#!/bin/bash

cat post-titles.csv | while read line
do
    for word in $line
    do
        echo $word | tr -cd '[:alnum:]\n'
    done
done

Cleanup and Sorting

Remove empty lines

sed -i '/^$/d' single-words.txt

Prepare stopwords

wget https://gist.githubusercontent.com/sebleier/554280/raw/ -O stopwords.txt

Remove stopwords from list I have so far.

cat single-words.txt | grep -v -Fix -f stopwords.txt|sort -rn|uniq -c|sort -rn|head -15

And that’s a wrap.

Could not establish trust relationship for the SSL/TLS secure channel.

Working with some older Cisco ASA devices, I’m trying to access the ASDM interface. The browser isn’t giving me luck, so I turned to PowerShell to help me, but I get the following error when trying an Invoke-WebRequest to grab the asdm.jnlp file I need.

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

Eh, ok. My first thought was to somehow avoid a certificate check but I did not see a native way of doing this with Invoke-WebRequest (at least from an old Server 2008 box with PowerShell v4.0).

StackOverflow to the rescue. Here’s the solution that worked for me.

if (-not("dummy" -as [type])) {
    add-type -TypeDefinition @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public static class Dummy {
    public static bool ReturnTrue(object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors) { return true; }

    public static RemoteCertificateValidationCallback GetDelegate() {
        return new RemoteCertificateValidationCallback(Dummy.ReturnTrue);
    }
}
"@
}

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [dummy]::GetDelegate()

Now I can add on my Invoke-WebRequest and everything works.

Get Enabled AD Users with Last Logon Time and Organizational Unit Information

This PowerShell script retrieves information about enabled Active Directory (AD) users, including their SAM account name, last logon time, and organizational unit (OU). The script makes use of several cmdlets and concepts that are common in PowerShell, including filtering, selecting, sorting, and transforming data.

Get-ADUser -Filter * -Properties lastLogon |
    Where-Object { $_.Enabled -eq $True } |
    Select-Object samaccountname, @{
        Name="lastLogon";
        Expression={[datetime]::FromFileTime($_.lastLogon)}
    }, @{
        Name="OU";
        Expression={( $_.distinguishedname -split ',' )[1].Split('=')[1]}
    } |
    Sort-Object OU |
    Where-Object { $_.OU -notmatch "CN=" }

Here is a detailed explanation of each part of the code:

  1. Get-ADUser cmdlet:

The Get-ADUser cmdlet is used to retrieve information about AD user objects. The -Filter parameter is used to specify that I want to retrieve all user objects, and the -Properties parameter is used to specify that I want to retrieve the lastLogon property.

  1. Where-Object cmdlet:

The Where-Object cmdlet is used to filter the results of the Get-ADUser cmdlet based on the Enabled property. In this case, I want to retrieve only those users that have their Enabled property set to $True.

  1. Select-Object cmdlet:

The Select-Object cmdlet is used to select specific properties from the filtered results. In this case, I want to select the samaccountnamelastLogon, and OU properties. The @{Name="lastLogon";Expression={[datetime]::FromFileTime($_.lastLogon)}} expression is used to convert the lastLogon property from a file time format to a more readable date/time format. The @{Name="OU";Expression={( $_.distinguishedname -split ',' )[1].Split('=')[1]}} expression is used to extract the name of the OU from the DistinguishedName property.

  1. Sort-Object cmdlet:

The Sort-Object cmdlet is used to sort the selected results based on the distinguishedname property. In this case, I want to sort the results in ascending order by the distinguishedname property.

  1. Where-Object cmdlet:

The final Where-Object cmdlet is used to further filter the sorted results based on the distinguishedname property. In this case, I want to retrieve only those results where the distinguishedname property does not match the string “CN=”.