Send email notification before Active Directory password expired

I am very glad to see there are more than 50,000 views in my blog when starting on counting “total visits” in my blog 7 months ago. Below are the top views from my blog.

Today, I want to share a small IT PowerShell script which is used to send notification to domain users before their password will be expired.

According to MS link (https://learn.microsoft.com/en-us/windows/win32/adschema/a-msds-userpasswordexpirytimecomputed), ms-DS-User-Password-Expiry-Time-Computed attribute is used to contain user password’s expire time in Active Directory. So, using the PowerShell code below to query this value in AD.

Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -Properties DisplayName, EmailAddress, msDS-UserPasswordExpiryTimeComputed

However, the value “133214896056482435” does not have any meaning, so we use [datetime]::FromFileTime($_.”msDS-UserPasswordExpiryTimeComputed”) to convert this value from FromFileTime to DateTime format in PowerShell.

# Query AD user 
$Users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -Properties DisplayName, EmailAddress, msDS-UserPasswordExpiryTimeComputed | `
Select-Object -Property DisplayName,  EmailAddress, @{Name="ExpirationDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | `
Sort-Object "ExpirationDate" 
$users | sort-object ExpirationDate

The PowerShell outputs a list of Domain users corresponding to “Display Name”, “EmailAddress” and “ExpirationDate”.

Next, check if the password will be expired soon (14 days) and send email notification to domain users.

# Query AD user 
$Users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -Properties DisplayName, EmailAddress, msDS-UserPasswordExpiryTimeComputed | `
Select-Object -Property DisplayName,  EmailAddress, @{Name="ExpirationDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | `
Sort-Object "ExpirationDate" 
#$users | sort-object ExpirationDate

# Check if the password will be expired soon and send email notification.
$UserList = foreach ($User in $users) {
if ($User.ExpirationDate -le (Get-Date).AddDays(14) -and $User.ExpirationDate -ge (Get-Date))  
 {
    # Create PSCustomObject to save a list of users whom will have a password expired soon
    [PSCustomObject]@{
        Name       = $User.DisplayName
        EmailAddress = $User.EmailAddress
        ExpiryDate = $User.ExpirationDate
        }
}
}
$userlist | sort-object ExpirationDate

PowerShell outputs a list of domain users who have the domain password will be expired soon.

Now, we will write an entire script to send email notification before AD password will be expired.

Get a password from the console, encrypt it and save into the file C:\Shared\admincred.txt

Read-Host -Prompt "Enter your tenant password" -AsSecureString | ConvertFrom-SecureString | Out-File "C:\Shared\admincred.txt" 
cat C:\Shared\admincred.txt

Below is a full PowerShell script to send email notification.

# PowerShell script that can send email notifications to domain users before their passwords will be expired.

# Query AD user 
$Users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -Properties DisplayName, EmailAddress, msDS-UserPasswordExpiryTimeComputed | `
Select-Object -Property DisplayName,  EmailAddress, @{Name="ExpirationDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | `
Sort-Object "ExpirationDate" 

# Check if the password will be expired soon and send email notification.
$UserList = foreach ($User in $users) {
if ($User.ExpirationDate -le (Get-Date).AddDays(14) -and $User.ExpirationDate -ge (Get-Date))  
 {
    # Create PSCustomObject to save a list of users whom will have a password expired soon
    [PSCustomObject]@{
        Name       = $User.DisplayName
        EmailAddress = $User.EmailAddress
        ExpiryDate = $User.ExpirationDate
        }
    # Send email
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $SMTP = "smtp-mail.outlook.com"
    $From = "admin@outlook.com"
    $username = "admin@outlook.com"
    #Read-Host -Prompt "Enter your tenant password" -AsSecureString | ConvertFrom-SecureString | Out-File "C:\Shared\admincred.txt" 
    $Pass = Get-Content "C:\Shared\admincred.txt" | ConvertTo-SecureString
    $cred = New-Object System.Management.Automation.PSCredential -argumentlist $username, $Pass
    $Subject = "Password Expiration Warning"
    $Body = "Dear $($User.DisplayName)," + "`n`nYour password will expire in $User.ExpirationDate. Please change your password as soon as possible." + "`n`nRegards," + "`nIT Department"
    $EmailBody = "Hello $($User.DisplayName)," + "`n`n" +
                 "This is a reminder that your password will expire in $($User.ExpirationDate)." + "`n`n" +
                 "Please take a moment to change your password to ensure continued access to your account." + "`n`n" +
                 "Thank you," + "`n" +
                 "IT Team"

   # Try to send the email, catch any exceptions, and log them
    Try {
   #   Send-MailMessage -From $From -To $User.EmailAddress -Subject $Subject -Body $EmailBody -smtpserver $SMTP -usessl -Credential $cred -Port 587
      $EmailMessage = @{
        From = $From
        To = $User.EmailAddress
        Subject = $Subject
        Body = $EmailBody
        SmtpServer = $SMTP
        UseSsl = $True
        Credential = $cred
        Port = 587
        }
      Send-MailMessage @EmailMessage      
    # Log success
      Add-Content -Path "C:\Shared\Logs\EmailSuccess.log" -Value "Email sent successfully to $($User.DisplayName) at $($User.EmailAddress) on $(Get-Date) "
    }
    Catch {
      # Write the error message to the file EmailFailure.log under C:\Shared\Logs.
      Write-Host "Error sending email to $($User.DisplayName) at $($User.EmailAddress): $_"
      Add-Content -Path "C:\Shared\Logs\EmailFailure.Log" -Value "Error sending email to $($User.DisplayName) at $($User.EmailAddress) on $(Get-Date) $_"
    }
}
}
$Userlist | sort-object ExpirationDate

Run the script.

C:\Users\Administrator\passwordnotify.PS1

Checking the EmailSuccess.log file, the script was successfully sent email notification to AD users before their password will be expired.

The script sent an email notification to AD users.

Finally, we can add the script into the Task Scheduler to check domain users’ password expiration and send email notification.

Trigger tab.

Actions tab.

Actions Settings:

Program: %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe

Arguments: -ExecutionPolicy Bypass -File C:\Shared\passwordnotify.PS1

The task was running successfully in Task Scheduler history.

Checking logs in the EmailSuccess.log file to confirm the task scheduler was running successfully.

The email notification was sent to Gmail’s account.

Count how many IP addresses in AWS US-East-1 region

In this article, I will demonstrate how to count how many IP addresses in US-East-1 region.

We can get a list of IP address from AWS.

https://ip-ranges.amazonaws.com/ip-ranges.json

Download and save the JSON file to aws-ip-ranges.json in the present working directory.

Invoke-WebRequest -Uri https://ip-ranges.amazonaws.com/ip-ranges.json -OutFile aws-ip-ranges.json

Load the JSON file and convert JSON formatted file to custom PSObject of PowerShell.

$data = Get-Content -Path aws-ip-ranges.json -Raw | ConvertFrom-Json

Extract the list of IP addresses for the US-East-1 region and AMAZON service.

$ip_addresses = $data.prefixes | Where-Object { $_.region -eq 'us-east-1' -and $_.service -eq 'AMAZON' } | Select-Object -ExpandProperty ip_prefix

Below is a PS script.

# This script is using to extract IP address in US-East-1 region of AWS
# Download and save the JSON file to aws-ip-ranges.json in the present working directory
Invoke-WebRequest -Uri https://ip-ranges.amazonaws.com/ip-ranges.json -OutFile aws-ip-ranges.json

# Load the JSON file and convert JSON formatted file to custom PSObject or Hashtable object of PowerShell
$data = Get-Content -Path aws-ip-ranges.json -Raw | ConvertFrom-Json

# Extract the list of IP addresses for the US-East-1 region1 and service is AMAZON
$ip_addresses = $data.prefixes | Where-Object { $_.region -eq 'us-east-1' -and $_.service -eq 'AMAZON' } | Select-Object -ExpandProperty ip_prefix

# Print the list of IP addresses of AWS US-East-1 Region
$ip_addresses

Run the script.

./aws-ip.PS1

Save all range IP addresses when running the script aws-ip.PS1 to aws-ip.txt

./aws-ip.PS1 > aws-ip.txt

Using the following PowerShell script to extract how many IP addresses in the Amazon CIDR subnet such as 52.4.0.0/14.

param(
    [string]$filename
)

function Count-IPs([string]$netblock) {
    # Get CIDR value from the netblock
    $cidr = [int]$netblock.Split('/')[1]
    # Return the number of IP addresses in the netblock above
    return [math]::Pow(2, 32 - $cidr)
}

if (!$filename) {
    # Display the error message if the filename parameter is not provided
    Write-Output "Usage: $($MyInvocation.MyCommand) <filename with CIDR masks>"
}

$ipcount = 0
Get-Content $filename | ForEach-Object {
    $ipcount += Count-IPs $_
}
Write-Output $ipcount

Run the script.

./countips.PS1 aws-ip.txt

There are 17,728,227 IP addresses in AWS US-East-1 region.

Using ChatGPT AI to learn PowerShell

ChatGPT is an AI chatbot system launched by OpenAI in November 2022. This is a great tool to help me understand more about PowerShell. Below are a couple of examples that I have used the tool to learn more PowerShell.

Can you show me top 10 commands in PowerShell?

Can you rewrite the script below?

$Hosts = Get-Content "C:\Scripts\servers.txt"
$Ports = '443'
ForEach ($HOSTLINE in $hosts) {
$status = Test-NetConnection $HOSTLINE -Port $ports -WarningAction SilentlyContinue
if ($status.tcpTestSucceeded -eq $True){
    Write-Output "$HOSTLINE is reachable"
}
else{
    Write-Output "$HOSTLINE is unreachable"  
}
}

Tell me an example for using foreach-object parallel with PowerShell remoting?

Does foreach-object parallel use multithreading?

Is there a limitation of foreach-object parallel?

Show me an example to use foreach-object parallel to check CPU usage in remote machines?

Can you rewrite the script below?

$inputpath = get-content "/var/log/auth.log"
#$inputpath = get-content "/Users/tungle/Downloads/auth.log.txt"
$regex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value } `
| Group-Object |  Select-Object Name, Count | Where-Object {$_.Count -gt 20}
ForEach($line in $output){
        $IP = $line.Name
        $result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IP
        # Sleep a second 
        Start-Sleep 1
    	#Write-Output $_count
        $myObject = [PSCustomObject]@{
        	Count = $line.Count
		"IPAddress" = $line.Name
            	#IP = $result.query
            	Country = $result.Country
}
    $myObject
}

ChatGPT – Here is a rewritten version of the script you provided:

# Set the file path for the log file
$logFilePath = "/var/log/auth.log"

# Read the log file and store the contents in a variable
$logContent = Get-Content -Path $logFilePath

# Set the regular expression for extracting IP addresses
$ipRegex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"

# Extract all of the IP addresses from the log file
$ipAddresses = $logContent | Select-String -Pattern $ipRegex -AllMatches | Select-Object -Expand Matches | Select-Object -Expand Value

# Group the IP addresses by count
$ipCounts = $ipAddresses | Group-Object | Select-Object Name, Count

# Filter the list to include only IP addresses that have failed login attempts more than 20 times
$filteredIpCounts = $ipCounts | Where-Object {$_.Count -gt 20}

# Loop through each IP address
foreach ($ipCount in $filteredIpCounts) {
    # Get the IP address and the number of failed login attempts
    $ip = $ipCount.Name
    $count = $ipCount.Count

    # Make a request to the IP address API to get information about the IP address
    $ipInfo = Invoke-RestMethod -Method Get -Uri "http://ip-api.com/json/$ip"

    # Sleep for a second to avoid making too many requests in a short period of time
    Start-Sleep 1

    # Create a custom object with the IP address, country, and number of failed login attempts
    $result = [PSCustomObject]@{
        "IPAddress" = $ip
        Country = $ipInfo.Country
        Count = $count
    }

    # Output the result
    $result
}

The script was provided by ChatGPT did not work as my script. However, the script is clear and easier to understand. So, I only do a small change in the script to make it work.

PS /tmp> cat ./chatgpt.PS1                          
# Set the file path for the log file
$logFilePath = "/var/log/auth.log"

# Read the log file and store the contents in a variable
$logContent = Get-Content -Path $logFilePath

# Set the regular expression for extracting IP addresses
$ipRegex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"

# Extract all of the IP addresses from the log file
$ipAddresses = $logContent | Select-String -Pattern $ipRegex -AllMatches | Select-String "failed password for root" | Select-Object -Expand Matches | Select-Object -Expand Value

# Group the IP addresses by count
$ipCounts = $ipAddresses | Group-Object | Select-Object Name, Count

# Filter the list to include only IP addresses that have failed login attempts more than 20 times
$filteredIpCounts = $ipCounts | Where-Object {$_.Count -gt 20}

# Loop through each IP address
foreach ($ipCount in $filteredIpCounts) {
    # Get the IP address and the number of failed login attempts
    $ip = $ipCount.Name
    $count = $ipCount.Count

    # Make a request to the IP address API to get information about the IP address
    $ipInfo = Invoke-RestMethod -Method Get -Uri "http://ip-api.com/json/$ip"

    # Sleep for a second to avoid making too many requests in a short period of time
    Start-Sleep 1

    # Create a custom object with the IP address, country, and number of failed login attempts
    $result = [PSCustomObject]@{
        "IPAddress" = $ip
        Country = $ipInfo.Country
        Count = $count
    }

    # Output the result
    $result
}
./chatgpt.PS1 | Sort-Object Count -Descending
./show-attacker.PS1 | Sort-Object Count -Descending

Count the number of failed root login in Linux server by IP address via PowerShell

Using (e)grep, cut, awk, sed to extract specific information in Linux logs is one of the daily tasks of Linux system administrator.

grep "Failed" '/var/log/auth.log' | grep -v root | awk -F 'from ' '{ print $2} ' | awk '{print $1}' | sort | uniq -c | sort -nr | while read COUNT IP

However, in this article, I want to demonstrate how to use PowerShell to extract the number of failed root login in Linux server.

Download the /var/log/auth.log example file via github (https://github.com/elastic/examples/blob/master/Machine%20Learning/Security%20Analytics%20Recipes/suspicious_login_activity/data/auth.log)

Save it under Downloads directory. Querying the content of the file via Get-content command.

$inputpath = get-content "/Users/tungle/Downloads/auth.log"

Now, I want to explain how to use PowerShell to extract specific information in the log file. If the number of failed attempts is greater than the LIMIT, then it will display count number, IP address, and Geolocation of the IP address.

Firstly, we need to know a format of the IP address via PowerShell regex.

$regex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"

Filtering all lines are matched the regex format of the IP address in auth.log file.

$output = $inputpath | Select-String -Pattern $regex -AllMatches

Selecting all lines are matched “failed password for root”.

$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" 

Following the MS link, I use matches method to populate the returned MatchCollection object (https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.matches?view=netframework-4.7.2). % is an alias of foreach-object.

$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches }

Next, getting IP addresses of the failed login in the log file.

$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value }

Group the IP address property together.

$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value } `
| Group-Object

Check if the number of failed attempts is greater than the LIMIT (>10 failed login attempts).

$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value } `
                    | Group-Object |  Select-Object Name, Count | Where-Object { $_.Count -gt 10 }

Using the PS script block below to detect attempted IP address, count, and country. Basically, the script will check all lines in the $output variable above and output the top IP address and county attempts.

ForEach($line in $output){
    $IP = $line.Name
    #$IP
    # Query Geolocaltion of the IP addresses via free API
    $result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IP
    # Create a PSCustomObject to save Count, IP address and Country attempts 
    $myObject = [PSCustomObject]@{
                Count = $line.Count
                "IP Address" = $line.Name
                #IP = $result.query
                Country = $result.Country
}
$myObject
}

Below is a PS script block to check the failed root attempts.

$inputpath = get-content "/Users/tungle/Downloads/auth.log"
#$regex = ‘\b\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}\b’
$regex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
#$inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root"
#$inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches }
$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value } `
                    | Group-Object |  Select-Object Name, Count | Where-Object { $_.Count -gt 10 }
ForEach($line in $output){
    $IP = $line.Name
    #$IP
    $result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IP
    # Create a PSCustomObject to save Count, IP address and Country attempts 
    $myObject = [PSCustomObject]@{
                Count = $line.Count
                "IP Address" = $line.Name
                #IP = $result.query
                Country = $result.Country
}
$myObject
}

This is a PS script (show-attacker.PS1) to run in a cloud-based Linux virtual machine.

$inputpath = get-content "/var/log/auth.log"
#$inputpath = get-content "/Users/tungle/Downloads/auth.log.txt"
$regex = "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
$output = $inputpath | Select-String -Pattern $regex -AllMatches | Select-String "failed password for root" | % { $_.Matches } | % { $_.Value } `
| Group-Object |  Select-Object Name, Count | Where-Object {$_.Count -gt 20}
ForEach($line in $output){
        $IP = $line.Name
        $result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IP
        # Sleep a second 
        Start-Sleep 1
    	#Write-Output $_count
        $myObject = [PSCustomObject]@{
        	Count = $line.Count
		"IPAddress" = $line.Name
            	#IP = $result.query
            	Country = $result.Country
}
    $myObject
}

Run the script in Linux server.

./show-attacker.PS1 | Sort-Object Count -Descending

Finally, we can extract specific information of authentication logs in Linux server by using PowerShell.

Query IP-based Geolocation via PowerShell

Using the following site to query IP Geolocation with no API key required (https://ip-api.com/).

$IPaddress = read-host "Enter IP address to check Geo-IP information"
$result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IPaddress
$result  

Create a PScustomObject to contain the Geo-IP information that you want to get.

$myObject = [PSCustomObject]@{
            IP = $result.query
            City = $result.City
            Country = $result.Country
            Region = $result.Region
            ISP = $result.isp
            }

Run a PS code.

$myObject | format-list

We can covert the output to Json.

$myObject | ConvertTo-Json

Create a Get-IPaddress function to query the IP-based Geolocation.

function Get-IPaddress {
    <#
    .SYNOPSIS
        Output the IP-based Geolocation of the given IP address via PowerShell.
    .DESCRIPTION
        This PowerShell script outputs the geographic location of the given IP address.
    .PARAMETER IPAddress
        Specifies the IP address
    .EXAMPLE
        PS> Get-IPaddress 8.8.8.8
    .INPUTS
        String
    .OUTPUTS
        PSCustomObject
    .NOTES
        Author:  Tung
        Date: 2022-11-15
    #>
    [CmdletBinding()]
    Param (
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0)]
     
        [string[]]
        $IPaddress = ""
    )
    BEGIN {}
    PROCESS {
        try {
            if ($IPaddress -eq "" ) {
                $IPaddress = read-host "Enter IP address to check Geo-IP information"
            }
            $API = @{
                Uri    = "http://ip-api.com/json/$IPaddress"
                Method = "Get"
            }
            $result = Invoke-RestMethod @API
            #$result = Invoke-RestMethod -Method Get -Uri http://ip-api.com/json/$IPaddress
            # Create a PSCustomObject to contain Geo-IP information
            $myObject = [PSCustomObject]@{
                IP      = $result.query
                City    = $result.City
                Country = $result.Country
                Region  = $result.Region
                ISP     = $result.isp
            }
        }
        catch {
            #"Error in line $($_.InvocationInfo.ScriptLineNumber): $($Error[0])"
            $_
        }
        finally {
            $myObject | format-list
            #$myObject | ConvertTo-Json
        }   
    }
}
Get-IPaddress 8.8.8.8 
Get-IPaddress 64.59.144.100

PowerShell Module for Telegram

Credit for TechThoughts for this PowerShell module.

https://www.techthoughts.info/poshgram-powershell-module-for-telegram/

Following a link below to install PoshGram – A PowerShell Module for Telegram

https://github.com/techthoughts2/PoshGram

#from a 6.1.0+ PowerShell session
[Net.ServicePointManager]::SecurityProtocol =[Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Force -Scope AllUsers
Install-Module -Name "PoshGram" -Scope CurrentUser

PS script to install PoshGram in Windows machines.

$computers = get-content C:\Scripts\computers.txt
foreach ($computer in $computers) {
    invoke-command -ComputerName $computer -ScriptBlock {
        if (-not(Get-InstalledModule PoshGram -Erroraction SilentlyContinue)) {
            #from a 6.1.0+ PowerShell session
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Force -Scope AllUsers
            Install-Module -Name "PoshGram" -Scope CurrentUser
        }
    }
}

Check PoshGram has been installed in Windows machines.

#Check PoshGram has been successfully installed in Windows machines
$computers = get-content C:\Scripts\computers.txt
foreach ($computer in $computers) {
    invoke-command -ComputerName $computer -ScriptBlock {
        Get-InstalledModule -name PoshGram
    }
}

Below is a PS code to send a message from PS to Telegram

#There is an example of calling PowerShell 6.1 from PowerShell 5.1 to send a Telegram message with PoshGram using dynamic variables in the message
$token = “#########:xxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx”
$chat = “-#########”
$test = "I am a test"
& '.\Program Files\PowerShell\6-preview\pwsh.exe' -command "& {Import-Module PoshGram;Send-TelegramTextMessage -BotToken $token -ChatID $chat -Message '$test';}"
#--------------------------------------------------------------------------
cd C:\Program Files\PowerShell\7
$token = "965385337:AAH9FHX92cHcMdkR8y2olckKUJtpdHAMfCA"
$chat = "824628585"
Send-TelegramTextMessage -BotToken $token -ChatID $chat -Message "Test from 5.1 calling 7+ to"

Checking the Test message in your Telegram channel.

Access Linux via PowerShell

Follow the link below to install PowerShell in Linux Ubuntu

https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.2

# Update the list of packages
sudo apt-get update
# Install pre-requisite packages.
sudo apt-get install -y wget apt-transport-https software-properties-common
# Download the Microsoft repository GPG keys
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
# Register the Microsoft repository GPG keys
sudo dpkg -i packages-microsoft-prod.deb
# Update the list of packages after we added packages.microsoft.com
sudo apt-get update
# Install PowerShell
sudo apt-get install -y powershell
# Start PowerShell
pwsh

Now, we will access Ubuntu via PowerShell on Windows Server 2016.

Install OpenSSH in Windows 2016.

Follow the link below to download OpenSSH-Win64-8.9.10.msi. Then double click to install OpenSSH.

https://github.com/PowerShell/Win32-OpenSSH/releases/tag/v8.9.1.0p1-Beta

An OpenSSH folder is created under C:\Program Files\OpenSSH directory.

We create a PS session from Windows Server 2016 to Linux Ubuntu (ubuntu-desktop.cisa.local) via OpenSSH.

$session = New-PSSession -HostName ubuntu-desktop.cisa.local -UserName tung -SSHTransport
The error message happens because the cmdlet doesn’t find the ssh.exe file.

To solve the issue:

  • Add the OpenSSH path into environment variable and restart Windows 2016 server
  • OR copy ssh.exe to C:\Windows and C:\Windows\System32 directory.

Establish a PS connection to Linux Ubuntu via OpenSSH.

$session = New-PSSession -HostName ubuntu-desktop.cisa.local -UserName tung -SSHTransport
Enter-PSSession $session
Invoke-Command -Session $session -ScriptBlock {Get-Process}
Invoke-Command -Session $session -ScriptBlock {ip a}
Invoke-Command -Session $session -ScriptBlock {netstat -antp}

It can be seen that an OpenSSH connection has been established between Windows server 2016 and Linux Ubuntu.

Query Windows current OS build via PowerShell

Below is a PS code that is used to query current OS build via PowerShell.

function Get-WindowsVersion{
[CmdletBinding()]

Param
(
    [Parameter(Mandatory=$false,
                ValueFromPipelineByPropertyName=$true,
                ValueFromPipeline=$true
                )]
    [string[]]$ComputerName = $env:COMPUTERNAME
)

BEGIN {}
PROCESS {

Foreach ($Computer in $ComputerName)
{ 
    try {
        #$ping = Test-Connection -computerName $Computer -count 1 -Quiet
        #$ping = Test-NetConnection -computerName $Computer -port 5985 -InformationAction SilentlyContinue
        #if($ping)
            #{
            #$code = Get-ItemProperty -PATH 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
            $Result = Invoke-Command -ComputerName $Computer -ScriptBlock {Get-ItemProperty -PATH 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'} -ErrorAction Stop 
            $properties = [ordered]@{
                    ComputerName = $Computer
                    'Windows Edition' = $Result.ProductName
                    'Version' = $Result.ReleaseId
                    'OS Build' = $Result.CurrentBuild + '.' + $Result.UBR}
        }  catch {
            #Write-Verbose "Coundn't connect to $computer"
            $properties = [ordered]@{
                    ComputerName = $Computer
                    'Windows Edition' = $null
                    'Version' = $null
                    'OS Build' = $null
                        } 
        } 
        finally {
            $obj = New-Object -Typename PSCustomObject -Property $properties
            Write-Output $obj 
        }
}  
}
}
Get-WindowsVersion -computer W2016-S1
Get-WindowsVersion -ComputerName @('W2016-S1','DC3','DC1','DC2','Win10-PC') | sort Version -Descending

(DC2 and DC3 are not running or we cannot access to the servers for some reasons . So, we will create a hash table and assign null value for these computers.)

Enable WinRM (PowerShell remoting) on domain environment via GPO

There are several steps to enable WinRM on domain environment.

  • Allow remote server management through WinRM service and create a WinRM listener in WinRM service.
  • Enable WinRM service
  • Allow WinRM connection via Windows FireWall

Create a new GPO (Enable WinRM) to enable WinRM on domain.

Right click the “Enable WinRM GPO”, select “Edit”. Go to Computer Configuration – Administrative Templates Windows Components – Windows Remote Management (WinRM) – WinRM Service. Then, select Enabled for the setting “Allow remote service management through WinRM”. Next, double click “Allow remote server management through WinRM” setting, enter asterisk (*) for both IPv4 filter and IPv6 filter as a screenshot below to create a WinRM listener for WinRM service. Click Apply and OK.

Next, move on to enable the WinRM Service on Windows domain-joined computer.

Go to Computer Configurations – Preferences – Control Panel Settings. Right-click in the Services window and click New – Service. Change Startup to Automatic (Delayed Start). Find and select the service name is WinRM. Select “Start service” from the “Service action” and then click OK.

Finally, configure Windows FireWall to allow traffic to WinRM service via TCP port 5985 (HTTP) or TCP port 5986 (over SSL).

Go to Computer Configuration – Windows Settings – Security Settings – Windows Defender Firewall with Advanced Security. Click on the “New Inbound Rule”to create a new inbound rule and select Windows Remote Management as a following screenshot.

Select “Domain/Private network rule” under rules setting to allow remote connections from only Private and Domain profile.

Leave the “Allow the connection” as a default setting in the next page. Then, click Finish to create the rule.

Link the GPO to Windows domain-joined computers.

Restarted Windows domain-joined computers to apply the GPO.

The port 5985 is enabled in Windows 10 machine.

From Windows 2016 server, test the port 5985 is reachable on Windows 10 PC.

Test-NetConnection -ComputerName Win10-PC -Port 5985

Test PowerShell Remoting is enabled on Windows 10 computer.

Invoke-command -ComputerNme Win10-PC -Scriptblock {hostname}
Enter-PSSession -computerName Win10-PC 
Invoke-command -ComputerName Win10-PC -Scriptblock {Restart-Computer}

Set up VPN IPSEC site-to-site between Palo Alto in AWS and FortiGate in premises

This is a diagram that I have used for this lab.

Understanding on deploying Palo Alto instance in AWS is necessary for this lab (https://tungle.ca/?p=3979).

On PA, Configure a tunnel.

Add a new static route into PA Virtual Route to allow traffic from the Private subnet to a LAN subnet in FortiGate.

Create IKE Crypto.
Create IPSEC Crypto.
Create an IKE Gateway.

Create an IPSEC tunnel.

Create PA-LAN and FG-LAN network.
Create both Security rules to allow traffic from PA-LAN to FG-LAN and vice versa.
Back to AWS – Route tables. Add a new static route on the Private Route.

Add 192.168.10.0/24 into the routes and select “Private Interface” on the target.

Move on to FortiGate.

Configure interfaces.

Configure default routes on FG.

Configure IPSEC VPN on FG.

Create a FG-LAN and PA-LAN address.
Set up a new static route to allow traffic from FG-LAN subnet in FG to PA-LAN subnet in AWS.
Create Security Polices to allow traffic from FG-LAN to PA-LAN and vice versa.
Setup IP address on Kali machine.

Ping from Kali machine to Windows instance (10.0.3.134).

Ping from Windows instance to Kali machine (192.168.10.2).

Check Security Policy status.
The FortiGate IPSEC tunnel is UP.

Back to Palo Alto in AWS. We can see the traffic from PA-LAN to FG-LAN and vice versa.

The Palo Alto IPSEC tunnel is UP.