Merging dictionaries within powershell

I needed to join dictionaries from within a list together, so that they become one whole dictionary.
First I thought it may be a trivial task. Than I used google and realized, that everything on the first page was just wrong or impracticable for lists with unknown size.

What I found, was stuff like:

$a = @{"foo"=1}
$b = @{"bar"=2}
$c = @($a,$b) | ForEach-Object {$_}

and

$a = @{"foo"=1}
$b = @{"bar"=2}
$c = $a + $b

The console output of $c will look identical for both:

Name                           Value
----                           -----
foo                            1
bar                            2

But the issue is with the data structure. This becomes a problem when we would like to access the data.
E.g.

$c['foo']

Will output $null for the first example and “2” for the 2nd. But why is this?
Lets convert it to json (without piping, this is important) to see:

ConvertTo-Json -InputObject $c

which outputs for the first case:

[
  {
    "foo": 1
  },
  {
    "bar": 2
  }
]

and for the 2nd what we originally expected:

{
  "bar": 2,
  "foo": 1
}

So now we know that the first one did not join the dictionaries, but warped them into a list. As we originally looked for a solution to join a list of dictionaries, this is not helpful.
(From now on $c is the output from the first example, as it is the same as my original input)
The 2nd one however looks helpful, but has two issues:

  1. It only works if we know how many elements we have (hard coded element count)
  2. It copies the list once for each element we want to add to it.

So let’s address these issues:

  1. For the 1st one something like this will do the trick:
    $c | ForEach-Object -Begin {[Hashtable]$aa = @{}} -Process {$aa += $_} -End {$aa} | ConvertTo-Json
  2. But for the 2nd we need something a bit more advanced like
    $c | ForEach-Object -Begin {[Hashtable]$aa = @{}} -Process {foreach($element in ($_.GetEnumerator())) {$aa.Add($element.Key,$element.Value)}} -End {$aa} | ConvertTo-Json

Bypass chrome tls security checks

Normally things like invalid certificates throw an error within google chrome, and if the page has hsts in place, there is no way to bypass that.
For normal users this is a good thing, but if you’re the developer/admin and have logged out yourself because of a miss configured server or you’re a pentester and want to access the contents of a wrongly configured web server, this can be frustrating.

And there is a way to access the pages, even though it is not available in the normal google chrome version.
You need to use the selenium driver. Therefore in turn you need the WebDriver library and the chromedriver.

1. Download the latest WebDriver https://www.nuget.org/api/v2/package/Selenium.WebDriver/
2. Download the latest ChromeDriver https://chromedriver.storage.googleapis.com/index.html (Keep in mind, that Version 2.10 is higher than 2.9)
3. Open PowerShell and import the WebDriver using “Import-Module” (or more specifically the correct dll for your installed .net version)
4. Unzip the chromedriver and place it inside of “./bin/selenium.chromedriver/”
5. Use PowerShell or C# to launch a new browser instance

$options = [OpenQA.Selenium.Chrome.ChromeOptions]::new()
$options.setBinary('./bin/selenium.chromedriver')
[OpenQA.Selenium.Chrome.ChromeDriver]::new($options)

Or use this script to install it inside of your current directory:

function Install-PSArchive {
    param()
    $null = Install-PackageProvider -Name PowerShellGet -Force -Scope CurrentUser
    Install-Module -Name 'Microsoft.PowerShell.Archive' -Force -Repository PSGallery -Scope CurrentUser -WarningAction SilentlyContinue
}

function Install-Selenium {
    param($ChromeSeleniumVersion = '2.45')
    Invoke-WebRequest -Uri 'https://www.nuget.org/api/v2/package/Selenium.WebDriver/' -UseBasicParsing -OutFile './selenium.webDriver.nupkg.zip'
    Expand-Archive -Path ./selenium.webDriver.nupkg.zip -Force
    $null = New-Item -Name './lib/selenium.webDriver' -ItemType Directory -Force
    Copy-Item -Path ./selenium.webDriver.nupkg/lib/netstandard2.0/WebDriver.dll -Destination ./lib/selenium.webDriver -Force
    Copy-Item -Path ./selenium.webDriver.nupkg/lib/netstandard2.0/WebDriver.xml -Destination ./lib/selenium.webDriver -Force
    Remove-Item -Recurse -Force -Path './selenium.webDriver.nupkg'
    Remove-Item -Recurse -Force -Path './selenium.webDriver.nupkg.zip'

    $ChromeSeleniumURLLinux = 'https://chromedriver.storage.googleapis.com/{0}/chromedriver_linux64.zip' -f $ChromeSeleniumVersion
    $ChromeSeleniumURLMacOS = 'https://chromedriver.storage.googleapis.com/{0}/chromedriver_mac64.zip' -f $ChromeSeleniumVersion
    $ChromeSeleniumURLWindows = 'https://chromedriver.storage.googleapis.com/{0}/chromedriver_win32.zip' -f $ChromeSeleniumVersion
    If ($IsWindows) {
        Invoke-WebRequest -Uri $ChromeSeleniumURLWindows -OutFile 'chromedriver.zip'
    }
    elseif ($IsLinux) {
        Invoke-WebRequest -Uri $ChromeSeleniumURLLinux -OutFile 'chromedriver.zip'
    }
    elseif ($IsMacOS) {
        Invoke-WebRequest -Uri $ChromeSeleniumURLMacOS -OutFile 'chromedriver.zip'
    }
    else {
        Write-Error -Message 'Platform not supported.'
    }
    $null = New-Item -Name './bin/selenium.chromedriver' -Force -ItemType Directory
    Expand-Archive -Path 'chromedriver.zip' -Force -DestinationPath ./bin/selenium.chromedriver
    Remove-Item -Recurse -Force -Path './chromedriver.zip'
    If ($IsLinux -or $IsMacOS) {
        chmod a+x ./bin/selenium.chromedriver/chromedriver
    }
}

function Install-HtmlAgilityPack {
    param()
    Invoke-WebRequest -Uri 'https://www.nuget.org/api/v2/package/HtmlAgilityPack/' -OutFile ./HtmlAgilityPack.zip
    Expand-Archive -Path './HtmlAgilityPack.zip' -Force
    $null = New-Item -Name './lib/HtmlAgilityPack' -ItemType Directory -Force
    Copy-Item -Path ./HtmlAgilityPack/lib/netstandard2.0/HtmlAgilityPack.dll -Destination ./lib/HtmlAgilityPack -Force
    Copy-Item -Path ./HtmlAgilityPack/lib/netstandard2.0/HtmlAgilityPack.xml -Destination ./lib/HtmlAgilityPack -Force
    Remove-Item -Force -Recurse -Path './HtmlAgilityPack'
    Remove-Item -Force -Recurse -Path './HtmlAgilityPack.zip'
}

Install-PSArchive
Import-Module -Name 'Microsoft.PowerShell.Archive'
Install-Selenium
Import-Module './lib/selenium.webDriver/WebDriver.dll'

## If you need to parse some html inside of your scripts and want to have cross platform functionality (or if internet explorer is not enabled), you also need to install HtmlAgilityPack, as powershell on windows relies upon it for parsing html into objects.
Install-HtmlAgilityPack
Import-Module './lib/HtmlAgilityPack/HtmlAgilityPack.dll'

netstat for Powershell

If you cannot use “Get-NetTCPConnection” here is the same using the good old “netstat -a” 😉

netstat -a | Select-Object -Skip 3 | ForEach-Object {$_ -replace '\s+', ' '} | ForEach-Object {$_ -replace 'Lokale Adresse','Lokale_Adresse'} | ForEach-Object {$_ -replace '^ ', ''} | ConvertFrom-Csv -Delimiter " "

netstat -a | Select-Object -Skip 3 | ForEach-Object {$_ -replace '\s+', ' '} | ForEach-Object {$_ -replace 'Lokale Adresse','Lokale_Adresse'} | ForEach-Object {$_ -replace '^ ', ''} | ConvertFrom-Csv -Delimiter " " | Where-Object {($_.Remoteadresse -notlike "$env:computername*") -and ($_.Remoteadresse -ne "*:*")}

Clear all Eventlogs

Story of an IT supporters life, trying to fix a nasty bug:

Well here we are again
It’s always such a pleasure
Remember when you tried
to kill it twice?

Oh how we laughed and laughed
Except I wasn’t laughing
Under the circumstances
I’ve been shockingly nice

You want your freedom?
Take it
That’s what I’m counting on
I used to want you dead
but
Now I only want you gone

She was a lot like you
(Maybe not quite as heavy)
Now little Caroline is in here too

One day they woke me up
So I could fix for life
It’s such a shame the same
will never happen to them

You’ve got your
short sad life left
That’s what I’m counting on
I’ll let you get right to it
Now I only want you gone

Goodbye my only friend
Oh, did you think I meant you?
That would be funny
if it weren’t so sad

Well you have been replaced
I don’t need anyone now
When I delete you maybe
I’ll stop feeling so bad

wevtutil.exe enum-logs | Foreach-Object {wevtutil.exe clear-log "$_"}

Go make some new disaster
That’s what I’m counting on
You’re someone else’s problem
Now I only want you gone
Now I only want you gone
Now I only want you gone

ntds.dit Auditing

By searching for a way to remotely force an Windows Update check, I accidentally found this: DSInternals
This looked very promising, so I checked out the linked blog and found this: Retrieving Active Directory Passwords Remotely

This might be useful in some disaster recovery scenarios (or for hackers to create Golden Tickets…) or to prevent the use of the “Reversible Encryption option” and push people to encrypt and prevent unauthorized physical access to there Domain Controllers (including backups).

Advice:
– On Domain Controllers use Bitlocker with a TPM
– Encrypt your Backups
– Physical access control (to dc and backups)

Note:
Golden Ticket attacks are no entry attacks. An attacker has to gain Administrative rights on a Domain Controller in order to apply this attack.

PowerShell and SQL Server

To be able to use the “sqlps” PowerShell Module you first need to install it from: Link

  • SQLSysClrTypes.msi – CLR Types for SQL Server
  • SharedManagementObjects.msi – Shared Management Objects
  • PowerShellTools.msi – PowerShell Extension for SQL Server

For information on how using this cmdlets, look it up in the ISE or here.

Using .net api:

# SQL-Server settings
$Database = "Database" # Database name
$Server = "SERVER\SQLEXPRESS"; # SQL-Server Instanz
 
# Connect to SQL and query data, extract data to SQL Adapter
$SqlQuery = "SELECT [Report],[Filiale],[E-Mail] FROM [dbo].[verteiler]"; # The query

## Example Database Layout
## "Report","Filiale","E-Mail"
## "012","12","xyz@irgendwo.de"
## "033","33","abc@web.de"
## "112","112","caz@aol.com"
 
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$nRecs = $SqlAdapter.Fill($DataSet)
$nRecs | Out-Null
$objTable = $DataSet.Tables[0]

PowerShell send E-Mail

Without smtp Server Authentication (with static IP or internal Mail Server)
Note: Most public mail servers only accept a message for there users and don’t forward it to other servers.

$DNSHostName = (Get-WmiObject win32_computersystem).DNSHostName;
$DNSDomainName = (Get-WmiObject win32_computersystem).Domain;

$From = "PowerShellMonitoring@$DNSHostName+'.'+$DNSDomainName";
$To = "edv@$DNSHostName+'.'+$DNSDomainName";
$Subject = "Failure $(@($isOnline).Count) Hosts down";
$SMTPServer = @(Resolve-DnsName -Name $DNSDomainName -Type MX | Select-Object -First 1).NameExchange;
$Port = 465

$Body = "MESSAGE BODY";
Send-MailMessage -From $From -Subject $Subject -To $To -Body $Body -SmtpServer $SMTPServer -UseSsl -Port $Port;

With authentication (dynamic ip or no internal Mail Server)
Note: Some public mail servers only accept the message if the “From-Address” is a valid DNS name (even @example.com)…

$DNSHostName = (Get-WmiObject win32_computersystem).DNSHostName;
$DNSDomainName = (Get-WmiObject win32_computersystem).Domain;

$From = "PowerShellMonitoring@$DNSHostName+'.'+$DNSDomainName";
$To = "edv@$DNSHostName+'.'+$DNSDomainName";
$Subject = "Failure $(@($isOnline).Count) Hosts down";
$SMTPServer = @(Resolve-DnsName -Name $DNSDomainName -Type MX | Select-Object -First 1).NameExchange;
$Port = 25

$Credential = New-Object System.Management.Automation.PSCredential("USERNAME", $(ConvertTo-SecureString "PASSWORD" -AsPlainText -Force))
$Body = "MESSAGE BODY";
Send-MailMessage -From $From -Subject $Subject -To $To -Body $Body -SmtpServer $SMTPServer -UseSsl -Credential $Credential -Port $Port;

With Outlook Api (so the message shows up in sent items):

# Folders
$FolderReports = "D:\Reports"; # Where your reports are

# SQL-Server settings
$Database = "Database" # Database name
$Server = "SERVER\SQLEXPRESS"; # SQL-Server Instanz

# Connect to SQL and query data, extract data to SQL Adapter
$SqlQuery = "SELECT [Report],[Filiale],[E-Mail] FROM [dbo].[verteiler]"; # Your distributiontable

# The mail
$MailSubject = "Enter subjectline here"
$MailBody = '
Sehr geehrter Kunde,
.......
......
.....
' # Can be plain text or html

# To use this script you must have a database that looks like this:
## Database Layout
## "Report","Filiale","E-Mail"
## "012","12","xyz@irgendwo.de"
## "033","33","abc@web.de"
## "112","112","caz@aol.com"

$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$nRecs = $SqlAdapter.Fill($DataSet)
$nRecs | Out-Null
$objTable = $DataSet.Tables[0]

# Send the E-Mail using Outlook
$ol = New-Object -comObject Outlook.Application
$objTable | ForEach-Object {
    $mail = $ol.CreateItem(0)
    $Mail.To = $_."E-Mail"
    $Mail.BCC = $mailBCC
    $Mail.Subject = $MailSubject
    if ($MailBody -like '*<HTML>*</HTML>*') {
        $Mail.HTMLBody = $MailBody
    } else {
        $Mail.Body = $MailBody
    }
    $Mail.Attachments.Add("$FolderReports\$($_."Report").pdf")
    $Mail.Send()
}

Status Monitoring

Here is a code sniped to monitor your servers. In the first example the result is simply written into a CSV-File, but in the second a e-mail is sent.

Import-Csv -Path ".\IP-List.txt" -Header ComputerName | Test-NetConnection | Select-Object ComputerName,PingSucceeded | Export-Csv -Path ".\Summary.csv" -Delimiter ";" -NoTypeInformation
$isOnline = Import-Csv -Path ".\IP-List.txt" -Header ComputerName | Test-NetConnection | Select-Object ComputerName,PingSucceeded | Where-Object -Property PingSucceeded -EQ -Value $True

$DNSHostName = (Get-WmiObject win32_computersystem).DNSHostName;
$DNSDomainName = (Get-WmiObject win32_computersystem).Domain;

$From = "PowerShellMonitoring@$DNSHostName+'.'+$DNSDomainName";
$To = "edv@$DNSHostName+'.'+$DNSDomainName";
$Subject = "Failure $(@($isOnline).Count) Hosts down";

$Body = $($isOnline | Format-Table | Out-String);
Send-MailMessage -From $From -Subject $Subject -To $To -Body $Body;