Search This Blog

Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts

Monday, April 21, 2025

PowerShell: Working with CSV files

I have previously tried to setup a way of working with databases with PowerShell. Recently I came across a way to use a simple CSV file to store data and then fetch it from PowerShell. This post will cover a few pointers I picked up along the way as well.

The first thing I did was to create the CSV file, which works as a rudimentary database for the purpose. I started by creating the datasheet in Excel and converting it to a CSV file. Perhaps you even have a complete file from the beginning that you got from some statistics page for example.

How you can work with a CSV file

The code was written on one computer first, where the file got a comma delimiter. Then I rewrote the code on another computer which had semicolon as delimiter, this caused trouble for my script. Because PowerShell defaults to the comma delimiter, so you need to specify that in your script when you run the Import-CSV cmdlet. Store the data in a variable and set the delimiter, that way you can return later and troubleshooting becomes easier if it has trouble reading the data. In my case it detected the file, but I got no indication what was wrong at first all that happened was that PowerShell didn't fetch the data from the CSV, most likely because it could find the individual columns.

Here is what it can look like if you have the script and the CSV in the same root folder. Look into the CSV file to see which delimiter to use.

$Imported_CSV = Import-CSV -path .\database.csv -delimiter ","

With this simple line you now have read in the data into your variable and you can start work with it. Some inspiration for this part come from this video. To count the entries, simply run $Imported_CSV.Count and see it as representing the amount of rows. With this knowledge you can also inspect individual lines by calling on their index, remember to count from 0 which is the first row of content, not the header. Using classic PowerShell index enumeration, [-1] is the last row. You can thus cook up something like this:

($Imported_CSV[-1].Name) to get the data in the name column for the last object. This requires you to know the column name in advance. To find out the column names you could run the following code:

$Imported_CSV[0].Psobject.properties.name

With the index, I could also change a specific datapoint. When I then call on the main variable again, it lists the changed datapoint. Using the Export-CSV command I could then also save the change to the file.

($Imported_CSV[1].Name) = "CMD";
$Imported_CSV # Shows the change #

You can extract all rows from one or more columns using a standard Select-Object, and with this you could apply different grouping, sorting and formatting approaches. Here is an example of getting the data:

$Imported_CSV | Select-Object -Property Age, Name

When you have designed your data segment (using the index and the properties for example), pipe your selected data into Export-CSV and set the delimiter you want to use. If you run Get-Process or Get-Service you could extract that data into a CSV as well.

The row in your Excel file (also the CSV) with the name of the columns (such as name, age, location) is called the header, if the top row is not the header row you can use the parameter -header for that. There are a few things to keep in mind when using this functionality which you can read more about here.

To add a column we can use the following code. Simply put it goes through each row and adds an extra "cell" on the rightmost side. You can give the $row variable a different name, but I liked its simplicity. The "Header" is simply the name of the new column. The property value is what value that should be given for all rows in the new column.

foreach ($row in $Imported_CSV) {Add-Member -InputObject $row -NotePropertyName "Header" -NotePropertyValue "DefaultValue"}

To add a new row to the CSV file you create a new object that is appended. For the new row you enter the values for each column.

$newrow = [pscustomobject]@{
Name = "PowerShell"
Age = "7.5.0"
Location = "PC"
}

$Imported_CSV += $newrow

Finally you can export your file as mentioned earlier through Export-CSV:

$Imported_CSV | Export-CSV -path "C:\temp\exportfile.csv" -NoTypeInformation -Delimiter "," -Encoding Default;

The script concept idea

My script lets the user search with a GUI that connects to the "database" file to work with it. The GUI lets you select different columns in which you can look for matching data. Column names could be "name", "age" and "location" for example and are connected to radio buttons. Essentially you tick a radio button corresponding to a column, you write a search word and then it gets all the data connected to that particular data point. An easy way to search for something you know, to get the rest of the data that you might not know.

The search function is tied to a search button. By using "$textbox.text" the search function matches that search term to the column that you chose. Finally it returns the data in another text box. In my example I decided that it should return all connected data.

Other functionality I added was a reset button that quickly resets the different buttons and text boxes. In the bottom I have a status bar that gives basic information and it could be used for more detailed error messages for example.

Conclusion

We can with simple means edit datapoints, create a new row or a new column. We can fetch and save the contents of a CSV file. Apart from this we can use our other knowledge of PowerShell to work with the data. Remember to create a backup of any important data before you start experimenting with it. 

Monday, December 2, 2024

PowerShell: Scan wifi range

This app was inspired by the project Wigle, where you can locate wifi routers in the world.
The script uses some functions for signal strength, some simple math for calculating approximate distance and Windows Forms for the GUI.

In this version the buttons have been created using a function, instead of hard coding every attribute for each button. It doesn't require admin rights to run. It does however require you to place the image file in the same directory and point the script toward it so you get a cool background image.

I was also able to package it into a .exe so it could be run as an app, using Iexpress. 

Enjoy!

<# Prerequisites #>
Add-Type -AssemblyName System.Windows.Forms 
Add-Type -AssemblyName System.Drawing

<# Main Form #>
$Form_Main_Window = New-Object System.Windows.Forms.Form
$Form_Main_Window.Size = New-Object system.drawing.size(670,400)
$Form_Main_Window.Text = "Wifi Tool 1.0"
$Form_Main_Window.FormBorderStyle = "FixedDialog"
$Form_Main_Window.StartPosition = "CenterScreen"
$Form_Main_Window.ForeColor = [System.Drawing.Color]::LightGreen;
$Form_Main_Window.BackColor = [System.Drawing.Color]::Black;
$Form_Main_Window.MaximizeBox = $False
$Img_Background = "$PSScriptRoot\background_network.png"
$Form_Main_Window.BackgroundImage = [System.Drawing.Image]::FromFile($Img_Background)
$Form_Main_Window.BackgroundImageLayout = [System.Windows.Forms.ImageLayout]::Stretch

function Set-StatusText {
    param ([string]$StatusText)
    $Txt_Status_Box.Text = $StatusText
}

function Create-Button {
    param (
        [string]$Text,[System.Drawing.Point]$Location,[System.Drawing.Size]$Size,[scriptblock]$ClickAction        
    )

    $button = New-Object System.Windows.Forms.Button
    $button.Text = $Text
    $button.Location = $Location
    $button.Size = New-Object System.Drawing.Size(100,20)

    $button.ForeColor = [System.Drawing.Color]::LightGreen
    $button.BackColor = [System.Drawing.Color]::Black
    $button.TextAlign = "MiddleLeft"

    if ($ClickAction) {
        $button.Add_Click($ClickAction)
    }

    return $button
}

function Scan-Wifi {
    param (
        [string]$wifiName
    )
    
    $wifi_data_in = netsh wlan show networks mode=bssid
    $wifi_signal_percentage = $null

    $wifi_data_in -split "`r?`n" | ForEach-Object {
        if ($_ -match '^SSID\s+\d+\s*:\s*(.+)$') {
            $ssid = $matches[1]
        }
        elseif ($_ -match '^\s*Signal\s*:\s*(\d+)%\s*$') {
            $signal = $matches[1]
            if ($ssid -eq $wifiName) {
                $wifi_signal_percentage = [int]$signal
            }
        }
    }

    return $wifi_signal_percentage
}

function Calculate-Distance {
    $percentage = Scan-Wifi -wifiName $Txt_Target_Box.Text;
    if (-not $percentage) {
        Set-StatusText -StatusText "Couldn't calculate distance"
        }
    $twodotfour = 150 - ($percentage * 1.5);
    $five = 70 - ($percentage * 0.7);
    $six = 50 - ($percentage * 0.5);
    return "`r`nSignal strength: $percentage%`r`n2.4 Ghz: $twodotfour meters`r`n5 Ghz: $five meter`r`n6 Ghz: $six meters";
}

function Scan-AllWifi {
    Set-StatusText -StatusText "Scanning nearby wifis";

    $wifi_data_in = netsh wlan show networks mode=bssid
    $results = @()

    $wifi_data_in -split "`r?`n" | ForEach-Object {
        if ($_ -match '^SSID\s+\d+\s*:\s*(.+)$') {
            $ssid = $matches[1]
        }
        elseif ($_ -match '^\s*Signal\s*:\s*(\d+)%\s*$') {
            $signal = $matches[1]
            if ($ssid) {$results += [PSCustomObject]@{SSID   = $ssid; Signal = "$signal%"}}
        }
    }

    # Filter, Sort, and Format the results in one step
    $outputText = ($results | Where-Object { -not [string]::IsNullOrWhiteSpace($_.SSID) } | 
                   Sort-Object -Property Signal -Descending | 
                   ForEach-Object {"{0,-30} {1,-10}" -f $_.Signal,$_.SSID}) -join "`r`n"

    # Save to file and update textbox
    $Txt_Main_Box.Text = $outputText
    Set-StatusText -StatusText "Log file created. Mainbox updated.";
}

function Loop-Scan {

    if ($Txt_Status_Box.Text -ne "Scanning") {

    # If not already scanning, set scanning status #
    Set-StatusText -StatusText "Scanning";
    # Loop the scan and update the meter #
    for ($i = 1; $i -le [int]$Txt_Loop_Box.Text; $i++) {$target_output = Scan-Wifi -wifiName $Txt_Target_Box.Text; $ProgressBar.Value = $target_output; Start-Sleep -Milliseconds 1500;}
    # Mark scanning as complete #
    Set-StatusText -StatusText "Scan complete";

    } else {
    Set-StatusText -StatusText "Scanning attempt failed"
    }

}

function Scan-Target {
    $Txt_Main_Box.Text = Calculate-Distance; 
    $target_output = Scan-Wifi -wifiName $Txt_Target_Box.Text;
    $ProgressBar.Value = $target_output;
    Set-StatusText -StatusText "Target was scanned";
}

function Quick-Export {
$Txt_Main_Box.Text | Out-File -FilePath "$PSScriptRoot\Wifi-log.txt";
Set-StatusText -StatusText "Quick export clicked";
}

function QuickImport-Log {
# File dialog or just quick import? #
Set-StatusText -StatusText "Quick import button clicked";
$ImportFile = Get-Content -Path $PSScriptRoot\wifi-log.txt -Delimiter "%";
$Txt_Main_Box.Text = $ImportFile;
}

function Export-ResultAs {
    Set-StatusText -StatusText "Export as clicked";
    $SaveFile_Window = New-Object System.Windows.Forms.SaveFileDialog;
    $SaveFile_Window.Filter = "Text files (*.txt)|*.txt";
    $SaveFile_Window.FileName = "wifi-log-custom.txt"
    $Export_File = $Null;
    if ($SaveFile_Window.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {$Export_File = $SaveFile_Window.FileName};
    if($Export_File) {$Txt_Main_Box.text | Out-File $Export_File};
}

function Import-Log {
    Set-StatusText -StatusText "Import log button clicked.";
    $ImportFile_Window = New-Object System.Windows.Forms.OpenFileDialog;
    $ImportFile_Window.Filter = "Text files (*.txt)|*.txt";
    $ImportFile = $null
    if ($ImportFile_Window.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
        $ImportFile = $ImportFile_Window.FileName;
        $Txt_Main_Box.Text = Get-Content -Path $ImportFile -Raw}
    }

function Reset-App {
Set-StatusText -StatusText "Reset button clicked"; 
$Txt_Main_Box.Text = ""; 
$Txt_Target_Box.Text = ""; 
$ProgressBar.Value = 0;
}

function Exit-App {
Set-StatusText -StatusText "Closing"; 
$Form_Main_Window.Close();
}

<# Main textbox #>
$Txt_Main_Box = New-Object System.Windows.Forms.TextBox
    $Txt_Main_Box.Location = New-Object System.Drawing.Size (50,50)
    $Txt_Main_Box.Size = New-Object System.Drawing.Size (395,260)
    $Txt_Main_Box.Text = "Scan to populate"
    $Txt_Main_Box.Multiline = $True
    $Txt_Main_Box.ForeColor = [System.Drawing.Color]::LightGreen;
    $Txt_Main_Box.BackColor = [System.Drawing.Color]::Black;
    $Form_Main_Window.Controls.Add($Txt_Main_Box)

<# Status textbox #>
$Txt_Status_Box = New-Object System.Windows.Forms.TextBox
    $Txt_Status_Box.Location = New-Object System.Drawing.Size (50,320)
    $Txt_Status_Box.Size = New-Object System.Drawing.Size (200,20)
    $Txt_Status_Box.Text = "App is running. Awaiting input."
    $Txt_Status_Box.Multiline = $True
    $Txt_Status_Box.ForeColor = [System.Drawing.Color]::LightGreen;
    $Txt_Status_Box.BackColor = [System.Drawing.Color]::Black;
    $Form_Main_Window.Controls.Add($Txt_Status_Box)

<# Target textbox #>
$Txt_Target_Box = New-Object System.Windows.Forms.TextBox
    $Txt_Target_Box.Location = New-Object System.Drawing.Size (560,80)
    $Txt_Target_Box.Size = New-Object System.Drawing.Size (100,20)
    $Txt_Target_Box.Text = "Enter a wifi name"
    $Txt_Target_Box.ForeColor = [System.Drawing.Color]::LightGreen;
    $Txt_Target_Box.BackColor = [System.Drawing.Color]::Black;
    $Form_Main_Window.Controls.Add($Txt_Target_Box)
    $Form_Main_Window.Add_Shown({
    $Txt_Target_Box.Focus()
    $Txt_Target_Box.SelectAll()
    })

<# Scan N textbox #>
$Txt_Loop_Box = New-Object System.Windows.Forms.TextBox
    $Txt_Loop_Box.Location = New-Object System.Drawing.Size (560,110)
    $Txt_Loop_Box.Size = New-Object System.Drawing.Size (50,20)
    $Txt_Loop_Box.Text = "10"
    $Txt_Loop_Box.Multiline = $True
    $Txt_Loop_Box.ForeColor = [System.Drawing.Color]::LightGreen;
    $Txt_Loop_Box.BackColor = [System.Drawing.Color]::Black;
    $Form_Main_Window.Controls.Add($Txt_Loop_Box)

<# Progress bar #>
$ProgressBar = New-Object System.Windows.Forms.ProgressBar
    $ProgressBar.Minimum = 0
    $ProgressBar.Maximum = 100
    $ProgressBar.Value = 0
    $ProgressBar.Location = New-Object System.Drawing.Point(50,10)
    $ProgressBar.Size = New-Object System.Drawing.Size(200,20)
    $ProgressBar.Style = "Continuous"
    $ProgressBar.Text
    $ProgressBar.ForeColor = [System.Drawing.Color]::Green
    $ProgressBar.BackColor = [System.Drawing.Color]::Red
    $Form_Main_Window.Controls.Add($ProgressBar)

<# Label for progress bar #>
$Lbl_ProgressBar = New-Object System.Windows.Forms.Label;
    $Lbl_ProgressBar.Location = New-Object System.Drawing.Point(255,10);
    $Lbl_ProgressBar.Size = $ProgressBar.Size;
    $Lbl_ProgressBar.Text = "Signal strength";
    $Lbl_ProgressBar.TextAlign = "MiddleLeft";
    $Lbl_ProgressBar.ForeColor = [System.Drawing.Color]::DarkGreen;
    $Lbl_ProgressBar.BackColor = [System.Drawing.Color]::Transparent;
    $Form_Main_Window.Controls.Add($Lbl_ProgressBar);

<# Scan all button #>
$Btn_Scan_All = Create-Button -Text "Scan All" -Location (New-Object System.Drawing.Point 450, 50) -ClickAction { Scan-AllWifi }
$Form_Main_Window.Controls.Add($Btn_Scan_All)

<# Scan target button #>
$Btn_Scan_Specific = Create-Button -Text "Scan target ->" -Location (New-Object System.Drawing.Point 450, 80) -ClickAction { Scan-Target }
$Form_Main_Window.Controls.Add($Btn_Scan_Specific)

<# Loop scan button #>
$Btn_Loop_Scan = Create-Button -Text "Loop scan ->" -Location (New-Object System.Drawing.Size 450,110) -ClickAction { Loop-Scan }
$Form_Main_Window.Controls.Add($Btn_Loop_Scan)

<# Quick Export button #>
$Btn_Quick_Export = Create-Button -Text "Quick Export" -Location (New-Object System.Drawing.Point 450, 200) -ClickAction { Quick-Export }
$Form_Main_Window.Controls.Add($Btn_Quick_Export)

<# Import Quick button #>
$Btn_Quick_Import = Create-Button -Text "Quick Import" -Location (New-Object System.Drawing.Point 555, 200) -ClickAction { QuickImport-Log }
$Form_Main_Window.Controls.Add($Btn_Quick_Import)

<# Export As button #>
$Btn_Export_As = Create-Button -Text "Export As" -Location (New-Object System.Drawing.Point 450, 230) -ClickAction { Export-ResultAs }
$Form_Main_Window.Controls.Add($Btn_Export_As)

<# Import specific button #>
$Btn_Import = Create-Button -Text "Import Log" -Location (New-Object System.Drawing.Point 555, 230) -ClickAction { Import-Log }
$Form_Main_Window.Controls.Add($Btn_Import)

<# Reset button #>
$Btn_Reset = Create-Button -Text "Reset" -Location (New-Object System.Drawing.Point 450, 260) -ClickAction { Reset-App }
$Form_Main_Window.Controls.Add($Btn_Reset)

<# Exit button #>
$Btn_Exit = Create-Button -Text "Exit App" -Location (New-Object System.Drawing.Point 450, 290) -ClickAction { Exit-App }
$Form_Main_Window.Controls.Add($Btn_Exit)

<# Display the window #>
$Form_Main_Window.ShowDialog()

Sunday, May 19, 2024

PowerShell: Check Admin Status, Self-replication and AutoRun

How to check if the script is run as administrator

It can be helpful to stop a script if it is not being run as an administrator, as some actions require administrative rights. You can use the check to trigger an informative window that the user instead should run the script as an admin.

# Check admin status #

$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator);

If ($isAdmin) {"Do the main part of the script"} else {"Inform the user that admin rights are missing"}

Simple self-replication

Sometimes we want a script to be able to replicate itself, for example, you might want the script to backup itself to a certain destination.

This code offers a simple way to replicate the script from itself.

First we define the path, $p, to be the script path itself.

Secondly, we test the path and if successful we extract the entire content from the script and send it to a destination that we also have specified. You can also include a simple error message.

This piece of code is formatted as a ternary operator, instead of a classic if-statement. The function is the same.

$p = "C:\Temp\testcopy.ps1"; 

(Test-Path $p) ? {Get-Content $p | Set-Content "C:\Temp\testcopy.txt"} : {"operation failed"};

It is possible to write the code to something else, you can for example append it to another file, or replace the content entirely.

AutoRun

AutoRun used to be a functionality, especially in older versions of Windows, where there was a file called AutoRun.inf located in the root folder of a CD or USB. This would tell the computer which file on the storage media to automatically start upon detection (when you plug in the USB / place the CD in the reader).

It is simple to make, create a text file with the following content:

[AutoRun]
open=your_program.exe
icon=your_program.ico

This is more like a legacy code, but you can still enable it. Here are two ways.

Reg file:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoDriveTypeAutoRun"=dword:000000FF

PowerShell:
# Enable AutoRun
$path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer'
Set-ItemProperty $path -Name NoDriveTypeAutoRun -Type DWord -Value 0xFF

# 0x0 instead of 0xFF would let you disable it instead.

Wednesday, May 1, 2024

PowerShell: Managing your Minecraft Server

My Minecraft server is running on a separate computer, simply to offset the resource requirements to a separate device.

When managing this server I use RDP to access the GUI and while on the management server I have simplified the management through some PowerShell scripts.

Here are the general outline of the scripts I have connected at this stage.

Currently in development:

Minecraft server start, a script that checks if the .exe is existing and if the process is running. If .exe exists but the process is not running, it starts the process.

Minecraft server stop, basically the reverse. It checks if the process is running, and if so, it issues a stop command to the server.

Minecraft server upgrade, this script is more advanced. It brings home the latest version (at this point hardcoded) and then performs a replacement action, making sure the properties and the world is not lost. Finally it inserts the backed up files again. Lastly it performs cleanup.

Scripts that are tried and tested:

Minecraft diagnostics, a script that performs a general health check. Are all files presents? Is the process running? What is the size of the Minecraft folder?

Minecraft backup, stops the server process, performs copies of the properties and the world, places them on a separate drive and then starts the process again. This is a vital component, because you need a regular backup and in theory you could add this to a schedule and include cleanup functionality.


Future script ideas:

Another project I am considering is a restore script, that combines functions from the backup, diagnostic and the upgrade, basically a script that checks for damage to the folder and perhaps compares with a baseline. Then perform basic replacement and cleanup actions to restore the folder to the best of its ability.

You can also use compare-object to check the contents of the properties for example, to check for corruption.

Last but not least, perhaps there is a way to perform a hashing fingerprint of the world.

Sunday, March 10, 2024

PowerShell: File backup script

This simple script allows you to backup a chosen folder to a set destination.

This script contains the main engine so to speak, but you can build further using this as a base. Some suggestions might be to add failsafe features or a GUI.

You can also add options to delete/manage existing backups as they exists as simple .zip-files.
Another option you might want to look into is how to revert to an older backup.

I used this script for backing up my Minecraft world, on my MC server. Basically I customized the base script to stop the bedrock_server service as well, as it was blocking the compression. Then it saved the world as a .zip file on a separate disk. This script checks if there was a backup already made today, but of course you can change this to perhaps create a more granular filename to avoid collisions 

Param(

  [string]$Path = 'C:\Temp\App',

  [string]$DestinationPath = 'D:\Backup\'

)

If (-Not (Test-Path $Path)) 

{

  Throw "The source directory $Path does not exist, please specify an existing directory"

}

$date = Get-Date -format "yyyy-MM-dd"

$DestinationFile = "$($DestinationPath + 'backup-' + $date + '.zip')"

If (-Not (Test-Path $DestinationFile)) 

{

  Compress-Archive -Path $Path -CompressionLevel 'Fastest' -DestinationPath "$($DestinationPath + 'backup-' + $date)"

  Write-Host "Created backup at $($DestinationPath + 'backup-' + $date + '.zip')"

} Else {

  Write-Error "Today's backup already exists"

}

Friday, January 19, 2024

Raspberry Pi 5: Ubuntu server installation

Prerequisites

To get Ubuntu Server on Raspberry Pi it was actually as easy as just getting the Raspberry Pi imager from the official website.

Install it and run it directly.

Choose device (raspberry pi 5), choose OS (other general-purpose OS -> Ubuntu -> Ubuntu Server)

Choose a device to flash it to.

You will have options to include into your file, these are important. You want to set up wifi, make sure SSH is active and set an admin login, this is used later when you SSH in with PowerShell and when you login to the server with RDP. 

Flash the OS to the SD card, you are now done with the first step.

First time booting Ubuntu Server on Raspberry Pi 5

Put the SD card in the device. Keyboard, mouse and monitor is optional as you can manage the device wirelessly and remotely right away if you performed the options detailed in part 1.

Go to your router and find the IP address of the device, this is used in next step.

Open PowerShell and write ssh admin@192.168.0.X where admin is the user name and X is the last part of the IP address. Confirm with password. SSH is a secure alternative to Telnet.

You will then be taken to an admin prompt that takes linux commands.
You might want to start with the following command:

sudo apt-get update && sudo apt-get upgrade

Make sure to confirm if it prompts you.  

GUI and RDP to Ubuntu Server

The last step was to configure a desktop experience and RDP.

For the deskop experience I simply ran two commands, it would seem that the first one is required.

sudo apt install ubuntu-desktop-minimal

sudo apt-get install xfce4

If prompted, I went with the option lightdm and I restarted all serviced that asked me too, some which also disconnected me from the internet.

For the RDP I first ran the following command:

sudo apt install xrdp -y

Followed by this command that shows you if the installation went alright.

sudo systemctl status xrdp

After that I could RDP using the built-in solution on Windows. Some sources claim you need to fix ssl certs and restart the service, but I could start it right away.

Configure your RDP from your Windows computer

Configure a RDP link on your desktop with the following steps:

1. Open remote desktop connection from your start menu

2. Pick "show options"

3. Computer should be the IP address, user name is the one you entered at the flashing of the image

4. Save as, place it in a good location. Now you can use it and just entering your password.

When you login using RDP you will be greeted with a more familiar desktop experience.


Enjoy!

Tuesday, December 26, 2023

PowerShell: Automating Cheats in Star Wars Knights of the Old Republic

Hello,

This blog post builds a bit on techniques such as sendkey/sendwait, allowing the PowerShell user to automate keystrokes which helps a lot when entering lengthy cheat codes into older games.
As I am using a nordic keyboard layout the way to open the Kotor 2 console is to press shift + backtick.
Keep in mind that you have to edit one of the files in the Steam folder to enable cheats for Knights of the Old Republic 2.

The PowerShell code:

# KOTOR 2 Auto Cheater #

# Sendkey functions #
$Backtick = "+(´)"
$Space = " "
$Enter = "{ENTER}"

<# Cheats hard codes from https://steamcommunity.com/sharedfiles/filedetails/?id=149123471
These variable each contains the actual cheat code of choice, giving you a long list of favorite codes.#>
$treat = "settreatinjury 40"
$compuse = "setcomputeruse 40"
$demo = "setdemolitions 40"
$stealth = "setstealth 40"
$aware = "setawareness 40"
$charm = "setcharisma 40"
$wis = "setwisdom 40"
$int = "setintelligence 40"
$const = "setconstitution 40"
$dex = "setdexterity 40"
$str = "setstrength 40"
$sec = "setsecurity 40"
$rep = "setrepair 40"
$pers = "setpersuade 40"
$exp = "addexp 999999"

# List used in loop, here you simply add variables that you want to include in the loop. #
$listofcheats = $treat, $compuse, $demo, $stealth, $aware, $charm, $wis, $int, $const, $dex, $str, $sec, $rep, $pers, $exp

# This function is responsible for opening the console only. One function, one purpose. #
function Open-Console {
[System.Windows.Forms.SendKeys]::SendWait($Backtick)
[System.Windows.Forms.SendKeys]::SendWait($Space)
}

# This function is responsible for using the cheat that was typed into the Kotor 2 console. #
function Send-Cheat {
[System.Windows.Forms.SendKeys]::SendWait($Enter)
}

<# This is a simple one of function, that accurately lets you reuse a single cheat code without the worry of misspelling #>
function Use-Cheat($code) {
Start-Sleep -Seconds 5;
Open-Console;
[System.Windows.Forms.SendKeys]::SendWait($code);
Send-Cheat;
}

# This is is the automatic function, it runs through the entire list you customize in the script. #
function Use-AllCheats {
    foreach ($cheat in $listofcheats) {
        Start-Sleep -Seconds 5;
        Open-Console;
        [System.Windows.Forms.SendKeys]::SendWait($cheat);
        Send-Cheat;
    }
}

Sunday, December 17, 2023

PowerShell: Create scheduled task

Case where a scheduled task comes in handy

Lately I've been suffering from a explorer process that is not starting upon boot of the computer.

I log in to Windows and the screen is completely black, apart from the mouse pointer sometimes. I've had this issue on a couple of computers over the years, while it is nothing dangerous and easy to fix, it is still annoying.

The quick fix is easy, start task manager using Ctrl + Shift + Escape and run the new task "explorer.exe".

However, I wanted to explore the PowerShell way of perhaps creating a scheduled task that simply checks if the explorer is running, and if not, apply the quick fix automatically. Also I explored whether I could use .bat-file to run a PowerShell script that registers this scheduled task. Unfortunately, for a quick and safe fix it didn't work, you either have to write a more complicated workaround or run the bat file as an admin, which then introduced some other issues. The goal was to keep it very simple.

The PowerShell script that is a failsafe for the explorer process

The code for the fix script looks like this:

function Start-Explorer {

    Start-Process explorer.exe;

}

$Explorer = Get-Process -Name explorer -ErrorAction SilentlyContinue

if (-Not($Explorer)) {

    Start-Explorer;

}

Manually scheduling a task in taskschd.msc

It is completely fine to save this to a .ps1-file and then manually create a scheduled task. Simply start task scheduler, hit "new basic task" once you are in the main folder of tasks.

Trigger:
Being logging in.

Action:
Start a program.

Program/script:
powershell.exe

The added arguments:
-windowstyle hidden -executionpolicy bypass -scope currentuser "C:\temp\PathToScript.ps1"

Basically this tells the computer that upon login (by the user creating the task), run the specified script while bypassing execution policy and only for the user, this time.

Scheduling a task using PowerShell, the basics

This is the very basic method of a PowerShell script that is intended to register a task in the task scheduler. When I tried dot sourcing it, it failed to bypass the execution policy and when I dot sourced it as an admin it created a scheduled task for all users of the computer. This could probably be altered with more detailed parameters and a permanent fix of the execution policy, but it is beside the point of this post. The point of this segment is to show the basic mechanics of using PowerShell to schedule a task.

$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-executionpolicy bypass -file `\"C:\Temp\Sample.ps1`\""
$trigger = New-ScheduledTaskTrigger -AtLogon
$task = New-ScheduledTask -Action $action -Trigger $trigger
Register-ScheduledTask NameOfTask -InputObject $task

As you can see it is a quite interesting and straightforward mechanic.

  1. The very first step is that you tell the computer what to do, in this case run a script with a few parameters, the extra symbols in the file path helps to character escape the extra quotes.
  2. Secondly you tell the computer when to do it, the trigger, in this case upon logging in.
  3. Third you combine the what and when into a task variable.
  4. Lastly you register this task, giving it a name and supplying the task variable to the cmdlet.
When working with PowerShell and the task scheduler, you can of course supply much more details, for example which user should be affected. However, from what I could see you are required to run this script as an admin if you want to run it from the console (dot sourcing).

Creating a .bat-file that runs a PowerShell script

As an added bonus and to wrap up the post of today, I will show you a simple way to use a bat file to kickstart a .ps1-file, with supplied parameters like execution policy bypass.

Open notepad, create a file with the following command:

powershell.exe -ExecutionPolicy Bypass -File .\ScriptInSameDirectory.ps1

You can add parameters like 
-nologo 
-windowstyle hidden 
-executionpolicy bypass and to specify current user add -scope currentuser

Note that if the .bat-file is not in the same folder as the target .ps1 you need to specify full path.

Good luck!

Sunday, November 26, 2023

Cryptography: Enciphering with PowerShell

The idea came to me after having followed some ARG's (alternate reality games), where people solve puzzles, often decoding some enciphered texts using more or lesser known cipher techniques, such as the classic Caesar Cipher, where you shift a letter in the alphabet a number of steps.

I wanted to see if I could create a script in PowerShell that would perform this action, after prompting ChatGPT a bit I managed to get the main logic behind the shifting.

This was put into an encoding function and a decoding function. Later on after the script matured, I decided to create a basic GUI for it, based off of the simple password generator I had created earlier.

This version offers you case cleaing, optional clipboard copy and decoding/encoding. In the future there might be some error handling put into the code, such as handling empty text fields.




This is what the code looks like:

# Caesar cipher /w GUI #


[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")


$Main = New-Object System.Windows.Forms.Form

$Main.text = "Caesar Cipher 1.1"

$Main.size = New-Object system.drawing.size(600,200)

$Main.BackColor = "DarkGray"

$Main.FormBorderStyle = "FixedDialog"


$PlainText = New-Object System.Windows.Forms.TextBox

$PlainText.Size = New-Object System.Drawing.Size(400,100)

$PlainText.Location = New-Object System.Drawing.Size(50,30)

$PlainText.BackColor = "Black"

$PlainText.ForeColor = "LightGreen"

$Main.Controls.Add($PlainText)


$CipherText = New-Object System.Windows.Forms.TextBox

$CipherText.Size = New-Object System.Drawing.Size(400,100)

$CipherText.Location = New-Object System.Drawing.Size(50,50)

$CipherText.BackColor = "Black"

$CipherText.ForeColor = "LightGreen"

$Main.Controls.Add($CipherText)


$Clipboard = New-Object System.Windows.Forms.CheckBox

$Clipboard.Size = New-Object System.Drawing.Size(80,40)

$Clipboard.Location = New-Object System.Drawing.Size(50,75)

$Clipboard.Checked = $False

$Clipboard.Text = "Clipboard"

$Main.Controls.Add($Clipboard)


$CipherKey = New-Object System.Windows.Forms.TextBox

$CipherKey.Size = New-Object System.Drawing.Size(30,50)

$CipherKey.Location = New-Object System.Drawing.Size(140,87)

$CipherKey.Text = "1"

$Main.Controls.Add($CipherKey)


function Encode-CaesarCipher {

    param ($pt, $ck)

    $pt = $PlainText.Text;

    $ck = $CipherKey.Text;

    $e = ""

    foreach ($c in $pt.ToUpper().ToCharArray()) {

        $x = [int][char]$c

        if ($x -ge 65 -and $x -le 90) { $e += [char](65 + (($x - 65 + $ck) % 26)) }

        elseif ($x -ge 97 -and $x -le 122) { $e += [char](97 + (($x - 97 + $ck) % 26)) }

        else { $e += $c }

    }

    $CipherText.text = $e

    if ($Clipboard.checked) {$e | Set-Clipboard}

}


function Decode-CaesarCipher {

    param ($ct, $ck)

    $ct = $CipherText.Text;

    $ck = $CipherKey.Text;

    $m = ""

    foreach ($c in $ct.ToUpper().ToCharArray()) {

        $x = [int][char]$c

        if ($x -ge 65 -and $x -le 90) { $m += [char](65 + (($x - 65 - $ck + 26) % 26)) }

        elseif ($x -ge 97 -and $x -le 122) { $m += [char](97 + (($x - 97 - $ck + 26) % 26)) }

        else { $m += $c }

    }

    $PlainText.text = $m

    if ($Clipboard.checked) {$m | Set-Clipboard}

}


$EncodeButton = New-Object System.Windows.Forms.Button

$EncodeButton.Size = New-Object System.Drawing.Size(60,40)

$EncodeButton.Location = New-Object System.Drawing.Size(180,87)

$EncodeButton.Text = "Encode"

$EncodeButton.BackColor = "Green"

$EncodeButton.ForeColor = "Yellow"

$EncodeButton.Add_Click({Encode-CaesarCipher;})

$Main.Controls.Add($EncodeButton)


$DecodeButton = New-Object System.Windows.Forms.Button

$DecodeButton.Size = New-Object System.Drawing.Size(60,40)

$DecodeButton.Location = New-Object System.Drawing.Size(250,87)

$DecodeButton.Text = "Decode"

$DecodeButton.BackColor = "Green"

$DecodeButton.ForeColor = "Yellow"

$DecodeButton.Add_Click({Decode-CaesarCipher;})

$Main.Controls.Add($DecodeButton)


[void] $Main.ShowDialog()

Sunday, September 17, 2023

Hardware: Finding device info using PowerShell

The hunt for more information began with a new keyboard I have. A wireless keyboard called "Deltaco TB-632". As an avid Deltaco fan I also knew that they rarely make their own products, but they rebrand devices that they source from other companies.

One example is their smart home devices that in at least one case seems to come from China.

So how did I go about finding more information about this keyboard? Well, old trusty PowerShell of course. After first trying to find a way using the control panel and settings to see a MAC address or other identifying information I simply turned to the command Get-Pnpdevice.

If you run this command you will discover practically all devices that has been connected to the computer since the last OS installation. They will be listed as Unknown if they aren't connected and OK if they are working fine.

This is the main code I ran: get-pnpdevice -Class USB | Select-Object status, friendlyname, instanceid | Sort-Object -Property friendlyname

First I ran the command without the USB wireless receiver attached.

Then I attached the receiver and ran the command again. By doing this I could see which device that turned from Unknown to OK.

In this specific case I got a result looking like this:

OK USB Composite Device USB\VID_248A&PID_8367\5&194485C0&0&1

Now that you have the VID (vendor ID) and PID (device ID) for your device you can simply Google these identifiers.

My result indicated that the Deltaco TB-632 seems to be provided by the company Maxxter and the USB device is called Telink Wireless Receiver.

Using the same method I tried looking for the keyboard unit itself as well in the list of PnP devices. I tested this theory by running the following command without and with the device plugged in.

(get-pnpdevice -Status OK).Count



The result was that 8 devices appeared when the receiver was plugged in.

To find out which devices that make up the difference you can combine Get-Pnpdevice with Compare-Object. Adapt the code after your individual situation.

First run this code with the device in:
 get-pnpdevice -Status OK | Sort-Object ClassGuid | Out-file -FilePath C:\temp\dev1.txt

Then run this code with the device out:
get-pnpdevice -Status OK | Sort-Object ClassGuid | Out-file -FilePath C:\temp\dev2.txt

To then compare the output run this comand:

Compare-Object -ReferenceObject (Get-content -path C:\temp\dev1.txt) -DifferenceObject (Get-Content -Path C:\temp\dev2.txt)

The output will look like this, the arrow shows that the result is present in the reference object, which in this case was to the left and missing in the difference object to the right, which means that they disappeared when we unplugged the device.


From here you can then investigate further from the VID and PID that you get.

Happy hunting :-)


Tuesday, June 6, 2023

PowerShell: Creating Forms

To create a simple GUI you can use Windows Forms, I've previously written a post on how I created a simple game using PowerShell and Windows Forms.

In this post I will rather talk a bit about the different components available, in the form of a reference guide for building various GUI-oriented scripts.

If you have worked with Tkinter in Python, you might be familiar with the concept of putting layer upon layer, or boxes within boxes, placed with a coordinate system. It's helpful to know but not at all a requirement. Let's dig in!

For some forms it is helpful to enter this at the start:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

Main window

For a basic app you will need a mothership that contains all the other elements such as buttons, boxes, text fields, images and the like. At the end you will also need activate the main window. The concept looks like this:

$MainWindow = New-Object System.Windows.Forms.Form
$MainWindow.text = "Stock Price Calculator"
$MainWindow.size = New-Object system.drawing.size(700,440)
$MainWindow.FormBorderStyle = "FixedDialog"

<Other code goes here>

$MainWindow.add_shown({$MainWindow.activate()})
[void] $MainWindow.ShowDialog()

In this example I've given the main window the variable name $MainWindow for the sake of simplicity. Add a dot to add additional attributes, such as .StartPosition = "CenterScreen".

Labels

Labels are pieces of text that describe something. Normally not something you want the user to interact with. It could for example describe what a textbox should contain. The location system is (x,y) where a lower x goes left and a lower y goes up. These numbers can be variables that you define outside the object. For example (($winwidth-100),($winheight-50)).

As with the main window variable, we can give labels a variable name. 

$Label_CurrentStock = New-Object System.Windows.Forms.label
$Label_CurrentStock.Location = New-Object System.Drawing.Size(100,222)
$Label_CurrentStock.Size = New-Object System.Drawing.Size(140,20)
$Label_CurrentStock.BackColor = "Gray"
$Label_CurrentStock.ForeColor = "Black"
$Label_CurrentStock.text = "Current amount of stock"
$Label_CurrentStock.textalign = "MiddleCenter"

In order for the object you create to show up on the main window add the following code. With the variable names that you use for your main window and object you are adding.

$MainWindow.Controls.Add($Label_CurrentStock)

Textbox

Textboxes are another object you might want to learn when creating Windows Forms. Just as with labels they are variables containing an object with attributes.

$Textbox_Current_Stock = New-Object System.Windows.Forms.TextBox
$Textbox_Current_Stock.Location = New-Object System.Drawing.Size(100,100)
$Textbox_Current_Stock.Size = New-Object System.Drawing.Size(140,20)
$MainWindow.Controls.Add($Textbox_Current_Stock)


Button with function

You also need intractability built into your script, so that the button for example triggers a calculation and presenting a result in a textbox. You can start off by defining the button, what size, location, color and text for example. Then you attach a function to it that is executed on being clicked.

This example shows a button that takes text from a textbox, makes it uppercase and then replaces the content in the textbox with the modified text. Here the textbox variable is called $TextboxMain and the property .Text is the content.

$ButtonUpper = New-Object System.Windows.Forms.Button
$ButtonUpper.Location = New-Object System.Drawing.Size(150,40)
$ButtonUpper.Size = New-Object System.Drawing.Size(100,200)
$ButtonUpper.TextAlign = "MiddleCenter"
$ButtonUpper.Text = "Upper Case"
$ButtonUpper.Add_Click({$TextboxMain.Text = ($TextboxMain.Text).ToUpper();})
$MainWindow.Controls.Add($ButtonUpper)

You can for example create a status bar in the bottom of your app that you write to in the same function, for example by writing $TextboxStatus.Text = "Text set to upper case"; or perhaps by turning something in your GUI green as a confirmation.

Saturday, May 27, 2023

Telnet: A quick overview

Overview

Telnet is a tool for administrating servers remotely. The name stands for Teletype Network and was invented pre-internet. As a consequence it is also an unencrypted way of communicating with a server. It should therefore not be used over the internet as the traffic may be intercepted, for example using Wireshark. 

Apart from servers it can also talk to other equipment such as network switches and routers. If the equipment is old, it might only be able to use Telnet instead of the encrypted tool called SSH (Secure Shell).

It is a command line tool that you can run on Windows, Mac and Linux which communicates bidirectionally. 

From a technical point of view it is a client/server type of protocol. The terminal captures keystrokes, the client converts it to a universal language that then goes through the TCP/IP protocol and through a network virtual terminal which sets a mutual standard for both machines.

It then goes through the TCP/IP stack on the receiving server side, the Telnet server converts/reverses the universal language to a language that the receiving system understands. The pseudoterminal then executes the commands and runs the applications.

Activating and deactivating Telnet Client

On a Windows machine you can activate it by going to the control panel, then select programs and features, then press "turn Windows features on or off".


Another way of activating Telnet is by using an elevated PowerShell prompt.

You can run the following commands to either activate or deactivate Telnet.

Enable-WindowsOptionalFeature -Online -FeatureName TelnetClient

Disable-WindowsOptionalFeature -Online -FeatureName TelnetClient


Using Telnet commands

If you write telnet followed by a target address followed by a space and a port number, you will use a different Telnet version. It can look like this if you try to connect to your local gateway:

telnet 192.168.0.1 23 

If you get stuck on "Connecting to 192.168.0.1..." it means that the port is closed and that Telnet won't work. Use the escape key to cancel. On a US keyboard it is Ctrl + ], on a Swedish keyboard your Telnet escape key is Ctrl + ¨.

Use telnet /? to open some the related help text.

You might experience lag when sending the commands, this is because the keystrokes has to travel back and forth over the network once you are connected via Telnet.


If only you write Telnet, you will instead open the Microsoft Telnet context.

To open a connection:
o google.com 443

To close a connection:
c google.com 443

To quit the Telnet context that you opened, simply use the command quit.

For more commands check the Microsoft page.

Summary

Telnet is an old and insecure way of communicating with servers, routers and switches. It is a text based tool run in the command prompt or PowerShell. Use SSH as a better alternative unless you work with legacy equipment that only can handle Telnet. Telnet is and should be disabled by default unless you have reasons to keep it active.


Sunday, April 30, 2023

Windows 11: Change boot logo

It all started with me wanting to change the boot logo of my Windows 11 computer.

The EFI (extensible firmware interface) is a protected, FAT32-drive with a size of 100 mb that usually is invisible to you in the file explorer. You can however see the partition in Disk management which you access as administrator through the program called Computer Management. At the end of the blog post I will show you how you can access it through PowerShell if you are curious. Always treat this partition with great respect.




My research led me to HackBGRT and here you are offered software that temporarily maps your EFI drive so that you can change the boot logo for one of your own.

How to design the logo


The requirement for the logo is that you have a 200 x 200 pixel image in the format of a 24-bit BMP image. It requires the name splash.bmp and that is has a black background if you want to achieve a "transparency effect", since the boot up background on most computers is black. 

In my experience it was better to have an icon that was wide on top of the square background, rather than trying to have a square icon as the image still became stretched out.

Use HackBGRT to change boot logo


  1. Download the .zip and extract it in a suitable folder.

  2. Perform a backup of your system through a system restore point, this is because changes in the EFI can destroy your boot up process.

  3. Make sure that you've created the image and placed it in a folder that you remember. You also need Paint installed on your computer.

  4. Run setup.exe in the HackBGRT folder, accept the UAC prompt and then press I to install the logo.

  5. Close the "readme.txt" that pops up, this will now open Paint. Using Paint now go to file and select open.
     
  6. Now you should have a file explorer window from Paint open. Navigate to your custom logo location, copy it, navigate to "This PC" and you should find a new A: drive. Navigate to the following: A:\EFI\HackBGRT and then paste the custom logo. You are prompted to replace existing file in destination, confirm to continue.

  7. Close Paint, exit the setup.exe window and then restart your computer to see if it work.

To restore your logo back to the original you run setup.exe and use R instead. 

Access the EFI through PowerShell

First we need to turn the EFI partition into a visible drive so that it becomes easier to work with.
  1. Open an elevated PowerShell window (run as administrator)
  2. Write Diskpart to activate the Diskpart program within PowerShell
  3. With Diskpart now running use the command and write list disk. This displays your available disks. Take note of with number your main disk has, usually 0.
  4. In order to target the right disk, write select disk 0 and get confirmation.
  5. In order to target the EFI partition we need to identify it. Write list partition and take note of the number, it is labeled as system type and has the size of around 100 mb. In my case it is partition number 1.
  6. To target the EFI partition we now write select partition 1 and get confirmation.
  7. In order to actually turn it into a visible drive write assign letter=E or another letter that is not in use.
  8. Write exit in order to leave the Diskpart program in the command line.
Continue in the elevated PowerShell window in order to navigate to the EFI drive, you can't access it through the file explorer as it is a FAT32 drive and regular access permissions doesn't seem to work there.
  1. Now that you know that the E: drive is the EFI you can use it for the following command.
  2. Write Set-Location "E:\EFI\" in order to step into that location.
  3. Write Get-ChildItem to list which subfolders exists within that location.
  4. Continue to explore subfolders using Set-Location and Get-ChildItem or their aliases sl and gci both which are easier to write but not recommended to use in scripts that are shared.
  5. Write exit to close the PowerShell window when you are done looking around. 




Monday, November 14, 2022

PowerShell: Password generating function

Passwords - the basics

Having a long password is usually better than just a complex short password. When attackers attempt to crack your password they will try different techniques such as brute forcing. That is when they have a dictionary of common passwords that they try to use to get into your account. Sometimes they will try one sequence of characters after another.

AAAA... AAAB.... ABCC....ABCD... and eventually the account goes "pop".

Just like a padlock there is a fixed amount of combinations.

A padlock has 0-9 per wheel usually. Which is ten characters per wheel. You can calculate the total amount of combinations like Characters per wheel to the power of the number of wheels.

Four wheels with 10 numbers (0 through 9) is 10 x 10 x 10 x 10. Which also is written as 10^4. When scripting in PowerShell you write [math]::Pow(10,4). Here is the answer, 10000 combinations.

Let's say that the brute forcing computer can guess 1000 passwords per second. That leaves your account safe for 10 seconds if you have a 4 character long password.

By increasing the variation of characters (increasing the numbers per wheel on your padlock) and increasing the amount of characters in your password (increasing the amount of wheels on your padlock) your password will take longer time to crack. 

However, store it well and preferably don't store it at all if possible. It doesn't matter if your password is 20 characters long and has a good complexity if it is stored in plain text in a file called "passwords.txt".

Those who store your password often store it "hashed" and "salted", to add extra complexity. Hashing uses a hashing function to scramble the password and salting adds random characters. If the passwords are leaked they will not be in plain text. Therefore some malicious actors will store pre-hashed password lists to match with the leaked hashed list of passwords to then compare the two.

Generating a password using PowerShell

I found myself having to update an old password and I didn't want to come up with a lot of numbers, letters and special characters myself. So I scripted a function that helps me to bring out a password with the requirements of being long and complex. (One difference between a function and a cmdlet is that the latter is compiled C# code.)

First I defined which characters I wanted to use in a string. Each character in a string can be called upon with an index number. The real engine behind this function is "Get-Random" which then returns a number no higher than the length of the string. This way you can get a random character.

The challenge was then stringing together a output string, the password, with the right amount of characters.

Finally I added a parameter that puts the password in my clipboard, one parameter that let's me pick the password length and also an alias for the function.

Here is the code:
 
function New-Password {

<# Parameters with default values #>
[alias("npwd")]
[cmdletbinding()]
    param
    (
    [Parameter()]
    [alias("Length")]
    [int]$DesiredPasswordLength = 15,
    [alias("c", "copy")]
    [switch]$CopyPassword = $False
    )

<# Available characters #>
[string]$AvailableCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!?@#";

<# Random letter from available characters #>
function Get-RandomLetter {
$AvailableCharacters[(Get-Random ($AvailableCharacters.Length))]
}

<# Build string #>
[string]$Password = ""
Do {$Password += Get-RandomLetter;} While ($Password.Length -lt $DesiredPasswordLength)
$PasswordLength = $Password.Length

<# Output #>
[float]$NumberOfCombinations = [math]::Pow($AvailableCharacters.Length,$Password.Length)
Write-Output "Your generated password is $Password and is $PasswordLength characters long. Complexity: $NumberOfCombinations combinations."
if ($CopyPassword) {$Password | Set-Clipboard} else {Break}

}

A few last thoughts


Remember that the code works with passwords in clear text, be mindful of how you implement it. Its purpose is to help you generate randomized strings of defined length.

Test your password security at sites like Passwordmonster or Security.org. I tested a random password with a length of 15 characters and it reached 100+ million years. How passwords will change in the face of quantum computing is for the future to tell. Perhaps password cracking will be faster or perhaps encryption will change fundamentally. Who knows?

Tuesday, November 8, 2022

PowerShell: Send email with attachment

It was when I was working on a hobby project that I did some research on how to send emails using PowerShell.

It wasn't the first time I managed to pull it off but I had forgotten how to script it, so I did some research.

Two parts at least seems to be needed, the credentials for the sender and the necessary variables for the email itself, such as header and content.

In this post I will discuss the credentials first, then the sending part. All this code was tested using PowerShell ISE. Microsoft writes that the cmdlet Send-Mailmessage is a bit outdated, so be wary.

Credentials

Rule number one in scripting: Don't store passwords in plain text, rule number two, don't store it in files that you send to others. Let's break these rules a bit.

We don't need to discuss why storing passwords in plain text is a bad idea, but this is how you can do it.
 
 
$u is your user name, in this case your full legit email address and $p is the plain text password.
Together the make up $c, which is a variable storing your credentials. Make sure to not enclose $p in quotation marks as this causes the code to break.

If you want to store your credentials in another way, you could write $c = Get-Credential; $c but it serves a different purpose than the automation and no sharing idea I had with my project.

Sending the email

This code shows your a cmdlet that takes related parameters (and a lot of them).
 
Send-MailMessage -To $receiver -From $u -Subject “Autoreply” -Body “What the main part of the mail contains.” -Attachments "c:\temp\log.txt" -Credential $c -SmtpServer “outlook.office365.com” -Port 587 -UseSsl

You need to find the right port and smtp server for your email provider. In this example I used Outlook. I couldn't send the mail without the "-usessl". Instead of a storing credentials in a variable, you can supply the "-credential" parameter with (Get-Credential) instead. The parenthesis helps prioritize the credentials.
 

Conclusion

Using the above code my script managed to send a file to the receiving email address.
However the script does not let you spoof the sender address provided in the send-mailmessage cmdlet. It has to be the same as the credential. I did not experience any trouble receiving the mail or attachments.


Thursday, November 3, 2022

Registry: Customize Windows 10

The registry in Windows keeps track of many program specific settings that you make changes to. In some way it is where the state of many things gets documented

You can change settings either through the regular Windows 10 GUI, or in the registry. Always be careful when editing within the registry itself.

To change settings through the registry you can either use "regedit.exe", use .reg-files or by using PowerShell. PowerShell gives you the ability to browse and edit like any other file structure.
 

Registry Editor (regedit.exe)

Regedit.exe takes you to a registry browser, that shows you what looks like folders and files. The top level of the registry contain HKEY_CURRENT_USER for example which is a "hive". The structure with folders under the hives are keys and subkeys respectively. The things looking like files are the values that have names and types.
 


Using .reg-files

The .reg-files use a simple syntax, they will add the "folders" that doesn't already exist and then they add the key with the desired value.
 
Create a notepad .txt file, enter code and rename it to .reg afterwards. You get a clickable and shareable file. 
 

 
Below is an example on how to disable Bing search in your Windows 10 start menu. Syntax looks like this:
 
Windows Registry Editor Version 5.00
 
[HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Explorer]
"DisableSearchBoxSuggestions"=dword:00000001 
 
The first part is what version of regedit your OS runs, then a necessary blank line, followed by an entry (hive, key, subkeys). It is possible to stack multiple edits in one file.

Another tweak I use to take me to the login page right away is the following:

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Personalization]
"NoLockScreen"=dword:00000001 
 
To show seconds in the clock on Windows 10 I use this tweak:
 
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
"ShowSecondsInSystemClock"=dword:00000001

Editing the registry with PowerShell

Using PowerShell to edit the registry is shown in this blog post I made earlier. The essential cmdlet that you work with is New-ItemProperty  
 
Followed by various parameters:
-path, where in the registry
-name the key/folder
-PropertyType whether it is a string or dword for example
-Value if there is text or perhaps a 1

Last but not least, in order for most things to take effect, you need to restart your computer.