Generating Easy and Secure Passwords in PowerShell

Hi Everyone,

So the other day, I found a much easier way to generate secure passwords in PowerShell. Before this, we had to have a list of all the available characters and put them into a CharArray, or ping an internet service like dinopass.com.

Not anymore!

From now on, whenever I need to generate a password in PowerShell, I will be using the

GeneratePassword()


Function from the [System.Web.Security.Membership] namespace. What this allows you to do, is generate a string of a specified length, with a specified amount of alphanumerical characters.

So if I wanted a password that was 10 characters long and had 5 alphanumerical characters, I would use:

[System.Web.Security.Membership]::GeneratePassword(10,5)


I usually just wrap that in a function because I’ve found you need to add the ‘System.Web’ assembly and it’s cleaner to add it in the function rather than the entire script. This is my new function:

function New-RandomPassword(){
}


Hope you learnt something from this 🙂

Using app.xaml and ResourceDictionaries for Cleaner WPF Customisation

When I first started making WPF forms, I was using PowerShell. This was a good starting point I think as I felt really comfortable with PowerShell which let me experiment more freely and break things in a way that I still felt comfortable.

However, this also meant putting all of my XAML into a single string. This might not sound too bad, but when you have five buttons all with slightly different templates and behaviours, the code quickly becomes messy and hard to read. Note that XAML is easy to read in the first place 😫 I was no stranger to having code that looked like this:

<Border Grid.Column="0" Grid.Row="1" Background="#FFE87E31" HorizontalAlignment="Right" Width="25" Height="25" Margin="0,0,2,0" CornerRadius="20"  BorderBrush="White" BorderThickness="1">
<Label Name="Search_Button" Cursor="Hand" Foreground="White" Content="🔍" FontSize="12" Width="25" Height="27" Margin="-1.667,-1.667,-0.334,-0.334" />
</Border>
<!-- OBJECT PANEL AND OBJECTS -->
<Border HorizontalAlignment="Stretch" Grid.Column="0" Grid.Row="2" VerticalAlignment="Stretch" Background="#FF34495F" >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- ALL OPTION OBJECTS HERE -->
<Border Height="35">
<Border.Style>
<Style>
<Setter Property="Border.Background" Value="#FF34495F"/>
<Style.Triggers>
<Trigger Property="Border.IsMouseOver" Value="True">
<Setter Property="Border.Background" Value="#FF1F2A36" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<Label Name="General_Information_Button" Cursor="Hand" VerticalContentAlignment="Center" Foreground="White" Content="General Information" FontFamily="Century Gothic" FontSize="14" />
</Border>

Horrible I know…

But when I moved to use C# with WPF, I found that I could have separate resources that could be used by multiple controls at the same time. You do this by adding a resource dictionary to the app.xaml file inside the WPF project.

Here is a quick example of what I mean. I created a Styles folder in the root of my WPF project and added a new ResourceDictionary(WPF). I called this resource dictionary “TextStyles” and it looks like this:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontSize" Value="28"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</ResourceDictionary>

Perfect, I then added this to my app.xaml file which now looks like this:

<Application x:Class="WPFUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFUI">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Styles/TextStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

From now on, all the text blocks in the WPF application would use the Arial font, use font size 28, be bold and also be white.

Important Notes:

1. It’s important to add your resource dictionaries to the app.xaml file in the correct order. You want to work from top to bottom. Meaning you don’t want a resource dictionary using something from another resource dictionary below it. For example, if you are using custom colours and want your text blocks to be that colour, you would put your colour resource first and then your text block resource.
2. If you don’t want a resource to be used everywhere in your application if you style inside the resource dictionary a name. Then you can reference the style inside your WPF XAML code.

Enjoy!

Saving Embedded WPF Resources as Files on Disk

In this post, I am going to show you how you can save an embedded resource as a file on disk. This is useful in certain scenarios. For example, I used this to save some background music onto the computer as the native MediaPlayer couldn’t use the application paths.

First things first, you want to make sure that you have set the Build Action to Embedded Resource in the properties for each of the items:

Once you have done that, we can go about creating a method to get the resource and save it to file. For me, that is as simple as:

public static void SaveMusicToDisk(){
//This creates a temp file in the %temp% directory called backgroundMusic.wav
using (FileStream fileStream = File.Create(Path.GetTempPath() + "backgroundMusic.wav")){

//This looks into the assembly and gets the resource by name
//For this to work, you need to use the full application path to the resource
//You get this by using your project name following by the folder tree to your item
Assembly.GetExecutingAssembly().GetManifestResourceStream("WPF.Assets.Sounds.backgroundmusic.wav").CopyTo(fileStream);
}
}

So that saves the file, what if I want to delete the file once the user wants to close the application?

For that I would use:

public static void DeleteMusicFromDisk(){
//This looks into the %temp% folder and deletes the file called "backgroundMusic.wav"
File.Delete(Path.Combine(Path.GetTempPath(), "backgroundMusic.wav"));
}

Peeeeerfect! 🎉 Does everything I need 😊

So, what if I wanted to save all the resources?

For that I could put all the resources into a string array and loop through them like this:

public static void SaveAllResources(){

//Gets all the resources associated with the assembly and puts them into an array
string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();

foreach (string resource in resources)
{
//Create a new file in the %temp% for each resource
using (FileStream fileStream = File.Create(Path.GetTempPath() + resource))
{
//Get the resource and save it to the current file
Assembly.GetExecutingAssembly().GetManifestResourceStream(resource).CopyTo(fileStream);
}
}
}

I hope you learn something or found this helpful. Enjoy!

PowerShell Classes and Class Lists

I found that I could use classes in PowerShell similar to how I use them in C#. I instantly wanted to play with this and I thought I would share this as well.

To create a class in PowerShell, it’s as simple as:

#Person class
class PersonClass{
[String]$Name [Int]$Age
}

This allows a “Person” to be created that has the attributed of a name and an age. Simple stuff.

Say I wanted to have a bunch of these “Person”s in a list, a “People” list if you will. Then I could do something like this:

#Creating a list to hold the people using the PersonClass
$People = New-Object 'System.Collections.Generic.List[PSObject]' #Creating a new person$newPerson = [PersonClass]::new()
$newPerson.Name = "Roy Orbison"$newPerson.Age = "24"

#Adding the new person to the people list
$People.Add($newPerson)

What if I wanted to add something like a “Pets” attribute onto the person? Well, I could create a new class to hold a framework for each pet and create a new list attribute in the PersonClass. Here is my PetClass:

#Pet class
class PetClass{
[String]$Name [Int]$Age
[String]$Color } And here is how I add it to my PersonClass so that I can have a list of pets for each user: #Person class class PersonClass{<br> [String]$Name
[Int]$Age [PetClass[]]$Pets
}

Now its really simple to create a list of people with a list of any pets that they might have. Stitching this all together, it looks like this:

#Person class
class PersonClass{
[String]$Name [Int]$Age
[PetClass[]]$Pets } #Pet class class PetClass{ [String]$Name
[Int]$Age [String]$Color
}

#Creating a list to hold the people using the PersonClass
$People = New-Object 'System.Collections.Generic.List[PSObject]' #Creating a new person$newPerson = [PersonClass]::new()
$newPerson.Name = "Roy Orbison"$newPerson.Age = "24"

#Adding pets to the new person
for ($i = 0;$i -le 5; $i++){$newPet = [PetClass]::new()
$newPet.Name =$i
$newPet.Age =$i + 2
$newPet.Color = "Brown" #Adding the pet to the new person$newPerson.Pets += $newPet } #Adding the new person to the people list$People.Add($newPerson)  Above you can see that I have created a new person called “Roy Orbison” with an age of “24” and I have added five pets. The pet names and age aren’t really accurate but it’s good enough for this demonstration. Continuing from this, I could add as many users as I want or even create new classes to add extra framework information for existing classes. Searching this information isn’t as straight forward in PowerShell as it is in C# but it’s still quite easy. You can see how I get a list of all the pets that Roy Orbison has below: $People | Where-Object {$_.Name -eq "Roy Orbison"} | Select-Object -ExpandProperty Pets Upon finishing this, I realised that it would have been much more appropriate to do the users and albums, instead of pets. But I’m far too lazy to change what I already have… Enjoy! Setting Up Caliburn.Micro MVVM This is a fairly lengthy post that shows how to set up an initial MVVM WPF form using Caliburn.Micro. Let’s get started, we’ll open up Visual Studio and chose to create a new WPF: Visual Studio -> New Project -> WPF App (.Net Framework) I usually set the project name to something like WPFUI and my solution name to be something like MVVMProject or the actual product name. For example, Microsoft might use Microsoft Outlook. Probably not, but you get what I’m saying… 1) First I will add Caliburn.Micro to my project. To do this go to Solution Explorer -> References -> Add NuGet Packages and search for Caliburn.Micro: 2) Now we can delete the MainWindow.xaml 3) Create a Views and ViewModels folder. This is where your viewmodels and views will call home. 4) Create a new class in the ViewModels folder and call it ShellViewModel and make it public. You’ll also want to inherit from screen and add the using statement for Caliburn.Micro: 5) Create a new window in the Views folder and call it ShellView 6) Create a new class in the root directory called Bootstrapper and inherit from BootstrapperBase and add the using statement for Caliburn.Micro 7) In App.xaml remove the StartupUri element and add the bootstrapper class as a resource by adding: <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <local:Bootstrapper x:Key="Bootstrapper" /> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> Your app.xaml should look like this: 8) Now go back into your Bootstrapper class and add the following: public Bootstrapper() { Initialize(); } protected override void OnStartup(object sender, StartupEventArgs e) { DisplayRootViewFor<ShellViewModel>(); } You’ll also need to add a using statement for System.Windows and ProjectName.ViewModels. The program should now launch and show an empty white screen with “ShellView” as the window’s title. This is the first entry into a hopefully long series into the world of Caliburn.Micro. Enjoy! 😊 Don’t use$input in PowerShell Functions

Just a short post and a gentle reminder to check conventions before pulling my hair out over a simple issue.

In PowerShell, this doesn’t work…

function Test-Function([string]$input){ Write-Host$input
}

After messing for about thirty minutes, I finally found this knowledge out and renamed my parameter. I think you’ll like the name I continued to use… 😊

function Test-Function([string]$stupidFuckingInput){ Write-Host$stupidFuckingInput
}

Enjoy!

Office Click-To-Run and XML Files

So, it used to be that we would install Office using a batch script that would invoke a setup.exe, assign a specific /configure flag and manually assign a specific XML file that contained the product that we wanted to install. This was bulky. It got too bulky when we needed to install 32-bit and 64-bit versions.

TIME FOR A CHANGE!

This is when I started thinking: “wow I really hate batch. I’m really glad I’m not the one that had to write this old script. Lets PowerShell this shit!”

First I needed a template XML file to modify, So this is what that looks like:

<Configuration>
<Language ID="MatchOS" />
</Product>
</Configuration>

This is the file that we will edit to say which product we want installing also if we want 64-bit or 32-bit.

Next, I needed to create a PowerShell script that would take a user’s input, edit the XML file accordingly and start the setup.exe with this flag. I also needed the bit-version that they wanted.

I started by defining the variables I would need for the script:

#Variables used for the installation
$bitVersion = ""$officeProduct = ""
$pathToOffice = "\\path\to\office\folder"$xmlFile = "OfficeXML.xml"
$pathToXMLFile = Join-Path -Path$pathToOffice -ChildPath $xmlFile Then I created a function I would use to update the XML file. I needed two parameters, the product that they wanted installing and the bit version they wanted: #Updates the XML file based on the input function Update-XMLFile([string]$product, [string]$bit){ try{ #Loading the XML document$xmlDoc = Get-Content -Path $pathToXMLFile #Edit the document$xmlDoc.Configuration.Add.OfficeClientEdition = $bit$xmlDoc.Configuration.Add.Product.ID = $product #Save the document$xmlDoc.Save($pathToXMLFile) }catch{$errorMessage = $_.Exception.Message Write-Host$errorMessage -ForegroundColor Red
Read-Host "The script encountered the above error - will now exit"
}
}

I then created another function to start the installation. This also required two parameters, the bit version and the XML file name

#Function to start the installation
function Start-Installation([string]$bit, [string]$xmlName){
try{
.\setup.exe /configure $bit\$xmlName
}catch{
$errorMessage =$_.Exception.Message
Write-Host $errorMessage Read-Host "The script encountered the above error - will now exit" } } My final function was a verification test. Since we want to only use 64-bit for future installations, I had to make sure that whoever was using the script knew this and would be competent enough to do a little bit of math: #Function to check the user wants 32 bit function Get-Verification(){$output = $false Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red Write-Host "All new installs should use 64-bit instead" Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"$firstNumber = Get-Random -Minimum 1 -Maximum 11
$secondNumber = Get-Random -Minimum 1 -Maximum 11$sumToCheck = $firstNumber +$secondNumber

$verificationInput = Read-Host "$($firstNumber) +$($secondNumber) = ?" if ($verificationInput -eq $sumToCheck){ Write-Host "Fine! 32-bit will be installed..."$output = $true }else{ Write-Host "Finally! 64-bit will be installed"$output = $false } return$output
}


Now that all my functions were defined, I could start with the actual meat of the script. This included cleaning the screen, asking the user some questions, launching the 32-bit verification is needed, updating the XML file using a switch statement and finally kicking off the installation. Heres what that looked like:

#Clear the screen
Clear-Host

#region Checking if the user wants 64 bit or 32 bit

do{

Write-Host "Do you want" -NoNewline
Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow
Write-Host "or" -NoNewline
Write-Host " 32-bit " -NoNewline -ForegroundColor Green
Write-Host "? (64 or 32): " -NoNewline
$bitVersionInput = (Read-Host).ToUpper() }while((64 ,32) -notcontains$bitVersionInput)

#endregion

#Check the user definitely wants 32 bit
if ($bitVersionInput -eq "32"){ if (Get-Verification){$bitVersion = $bitVersionInput }else{$bitVersionInput = "64"
}
}

#Update the bitVersion variable
$bitVersion =$bitVersionInput

#region Asking what product to install

#Ask the user what product they want to install
Write-Host @"

Please select one product from the below list

"@

Write-Host @"
2) ProPlus Retail

"@ -ForegroundColor Cyan

Write-Host @"
3) Visio Std Volume
4) Visio Pro Volume
5) Visio Pro Retail

"@ -ForegroundColor Green

Write-Host @"
6) Project Std Volume
7) Project Pro Volume
8) Project Pro Retail

"@ -ForegroundColor Gray

Write-Host @"
C) Cancel

"@ -ForegroundColor Red

do{
$officeProductInput = (Read-Host "Enter a number").ToUpper() }while((1,2,3,4,5,6,7,8, "C") -notcontains$officeProductInput)

#endregion

#Update the product variable
$officeProduct =$officeProductInput

#region Switch the input to see what it is and perform the required operation

switch($officeProduct){ #Business Retail 1 { Update-XMLFile -product "O365BusinessRetail" -bit$bitVersion}
#ProPlus
2 { Update-XMLFile -product "O365ProPlusRetail" -bit $bitVersion} #Visio Std Volume 3 { Update-XMLFile -product "VisioStd2019Volume" -bit$bitVersion}
#Visio Pro Volume
4 { Update-XMLFile -product "VisioPro2019Volume" -bit $bitVersion} #Visio Pro Retail 5 { Update-XMLFile -product "VisioPro2019Retail" -bit$bitVersion}
#Project Std Volume
6 { Update-XMLFile -product "ProjectStd2019Volume" -bit $bitVersion} #Project Pro Volume 7 { Update-XMLFile -product "ProjectPro2019Volume" -bit$bitVersion}
#Project Pro Retail
8 { Update-XMLFile -product "ProjectPro2019Retail" -bit $bitVersion} #Cancel "C" {Exit} default {Exit} } #endregion #Start the installation Write-Host "Installing..." -ForegroundColor Green Start-Installation -bit$bitVersion -xmlName $xmlFile Write-Host "This window can be closed" Read-Host Done! If you’re wondering what the script looks like as a whole, wonder no longer: #Variables used for the installation$bitVersion = ""
$officeProduct = ""$pathToOffice = "\\sandpdc\software\Office"
$xmlFile = "OfficeXML.xml"$pathToXMLFile = Join-Path -Path $pathToOffice -ChildPath$xmlFile

#Updates the XML file based on the input
function Update-XMLFile([string]$product, [string]$bit){

try{
$xmlDoc = Get-Content -Path$pathToXMLFile

#Edit the document
$xmlDoc.Configuration.Add.OfficeClientEdition =$bit
$xmlDoc.Configuration.Add.Product.ID =$product

#Save the document
$xmlDoc.Save($pathToXMLFile)
}catch{
$errorMessage =$_.Exception.Message
Write-Host $errorMessage -ForegroundColor Red Read-Host "The script encountered the above error - will now exit" } } #Function to start the installation function Start-Installation([string]$bit, [string]$xmlName){ try{ .\setup.exe /configure$bit\$xmlName }catch{$errorMessage = $_.Exception.Message Write-Host$errorMessage
Read-Host "The script encountered the above error - will now exit"
}
}

#Function to check the user wants 32 bit
function Get-Verification(){
$output =$false

Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red
Write-Host "All new installs should use 64-bit instead"
Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"

$firstNumber = Get-Random -Minimum 1 -Maximum 11$secondNumber = Get-Random -Minimum 1 -Maximum 11

$sumToCheck =$firstNumber + $secondNumber$verificationInput = Read-Host "$($firstNumber) + $($secondNumber) = ?"

if ($verificationInput -eq$sumToCheck){
Write-Host "Fine! 32-bit will be installed..."
$output =$true
}else{
Write-Host "Finally! 64-bit will be installed"
$output =$false
}
return $output } #Clear the screen Clear-Host #region Checking if the user wants 64 bit or 32 bit do{ Write-Host "Do you want" -NoNewline Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow Write-Host "or" -NoNewline Write-Host " 32-bit " -NoNewline -ForegroundColor Green Write-Host "? (64 or 32): " -NoNewline$bitVersionInput = (Read-Host).ToUpper()
}while((64 ,32) -notcontains $bitVersionInput) #endregion #Check the user definitely wants 32 bit if ($bitVersionInput -eq "32"){
if (Get-Verification){
$bitVersion =$bitVersionInput
}else{
$bitVersionInput = "64" } } #Update the bitVersion variable$bitVersion = $bitVersionInput #region Asking what product to install #Ask the user what product they want to install Write-Host @" Please select one product from the below list "@ Write-Host @" 1) Business Retail 2) ProPlus Retail "@ -ForegroundColor Cyan Write-Host @" 3) Visio Std Volume 4) Visio Pro Volume 5) Visio Pro Retail "@ -ForegroundColor Green Write-Host @" 6) Project Std Volume 7) Project Pro Volume 8) Project Pro Retail "@ -ForegroundColor Gray Write-Host @" C) Cancel "@ -ForegroundColor Red do{$officeProductInput = (Read-Host "Enter a number").ToUpper()
}while((1,2,3,4,5,6,7,8, "C") -notcontains $officeProductInput) #endregion #Update the product variable$officeProduct = $officeProductInput #region Switch the input to see what it is and perform the required operation switch($officeProduct){

1 { Update-XMLFile -product "O365BusinessRetail" -bit $bitVersion} #ProPlus 2 { Update-XMLFile -product "O365ProPlusRetail" -bit$bitVersion}
#Visio Std Volume
3 { Update-XMLFile -product "VisioStd2019Volume" -bit $bitVersion} #Visio Pro Volume 4 { Update-XMLFile -product "VisioPro2019Volume" -bit$bitVersion}
#Visio Pro Retail
5 { Update-XMLFile -product "VisioPro2019Retail" -bit $bitVersion} #Project Std Volume 6 { Update-XMLFile -product "ProjectStd2019Volume" -bit$bitVersion}
#Project Pro Volume
7 { Update-XMLFile -product "ProjectPro2019Volume" -bit $bitVersion} #Project Pro Retail 8 { Update-XMLFile -product "ProjectPro2019Retail" -bit$bitVersion}
#Cancel
"C" {Exit}
default {Exit}
}

#endregion

#Start the installation
Write-Host "Installing..." -ForegroundColor Green
Start-Installation -bit $bitVersion -xmlName$xmlFile
Write-Host "This window can be closed"
Read-Host

Quering and Adding Info To Access Database Using C#

In this post, I will show you how I created a program to extract and add data to an Access database. Before we get started, you can see my current specifications below:

Getting values from a table:

Using System.Data.OleDB;

//Create a new list to hold all the values
List<String> values = new List<String>();

//Build the connection string and SQL string
string connectionString = @$"Provider=Microsoft.ACE.OLEDB;Data Source = C:\Path\To\Access.accdb"; string sqlString = "SELECT * FROM Table_Name"; //Create a new connection to the Access file using (OleDbConnection connection = new OleDbConnection(connectionString)){ //Creating a new command OleDbCommand command = new OleDbCommand(sqlString, connection); //Try/catch to catch errors, DON'T DO THIS IN SERIOUS PROJECTS! try{ //Opening the connection and reading the data connection.Open(); using(OleDbDataReader reader = command.ExecuteReader()){ while(reader.Read()){ //Adding the value to the values list values.Add(reader["Field_Name"].ToString()); } } }catch{ } //Closing the connection connection.Close(); } //Sorting the list in ascending order values.Sort(); Adding a new row to the table: Using System.Data.OleDb; //Building the connection string and SQL string string connectionString = @$"Provider=Microsoft.ACE.OLEDB.12.0;Data Source = C:\Path\To\Access.accdb";
string sqlString = \$"INSERT INTO Table_Name(Field_Name1, Field_Name2) VALUES ('{Field_Value1}','{Field_Value2}')";

//Creating a new connection to the Access file
using (OleDbConnection connection = new OleDbConnection(connectionString))
{
//Build a new command
using(OleDbCommand command = new OleDbCommand(sqlString, connection))
{
//Open the database connection and execute the write
connection.Open();
}
//Close the database connection
connection.Close();
}

Enjoy!

Launch Chrome and Other Applications in C#

Hi Everyone,

Short post, I just wanted to put down into writing how I open Chrome windows and other applications in C# code. This is used by one of my startup programs which allowed me to open all my most used applications with ease 🙂

First I create a Process variables using the System.Diagnostics resource:

Process process = new Process();

I then point the process variables start info filename to the location of my Chrome executable:

process.StartInfo.FileName = @"C:\Path\To\Chrome.exe";

I then add my URL to the start info arguments:

process.StartInfo.Arguments = @"https://bbc.co.uk";

Then, if I want the link to open as a new window instead of onto an existing Chrome window I use:

process.StartInfo.Arguments += " --new-windows";

Finally, I start the process:

process.Start();

Here are some different scenarios:

Opening a URL in an existing Chrome window:

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\Chrome.exe";
process.StartInfo.Arguments = @"https://bbc.co.uk" ;
process.Start()

Opening a URL in a new Chrome window:

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\chrome.exe";
process.StartInfo.Arguments = @"https://bbc.co.uk";
process.StartInfo.Arguments += " --new-window";
process.Start();

Opening multiple URLs in a new Chrome window:

List<String> URLs = new List<String>(){
"https://bbc.co.uk",
"https://mharwood.uk",
}

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\chrome.exe";
foreach (string i in URLs)
{
process.StartInfo.Arguments += i;
}
process.StartInfo.Arguments += " --new-window";
process.Start();

Opening different programs:

List<String> Programs = new List<String>(){
@"C:\Path\To\First\Program.exe",
@"C:\Path\To\Second\Program.exe",
@"C:\Path\To\Third\Program.exe"
}

foreach(string i in Programs){
Process.Start(i);
}

I hope this is useful information for someone. Enjoy!