Oct 13

Shrink a Raspberry Pi or RetroPie .img On Windows with PiShrink

PiShrink is a bash script that automatically shrink a pi image that will then resize to the max size of the SD card on boot. However, thanks to Windows Subsystem for Linux (WSL), you can now run it on Windows!

Prerequisites

  • Install Linux on Windows with WSL
  • Rasberry Pi / RetroPie backup .img
    • An .img backup of a MicroSD includes all of the unused space and will be the size of the Micro SD Card (ie: a 16GB MicroSD Card will create about a 16GB .img backup). The simplest method for Windows is to use Win32 Disk Manager. Windows cannot read an EXT4, so it should show the Micro SD card as a Boot Drive (with a disk size of about 250MB) and another drive (with no disk size). Make sure you backup the second drive without a size listed in WIn32 Disk Manager, and this will backup the entire Micro SD card.
    • Note: Micro SD cards can vary slightly in size by manufacturer as well as between different models of Micro SD cards. Therefore, there’s no guarantee creating an .img backup of a Micro SD Card will be able to write back to the same size Micro SD card unless it’s the exact same manufacturer/model.
  • PiShrink.sh script
    • PiShrink will shrink an .img backup of Raspberry Pi / Retropie by removing the “unused” space. For example, if you create a backup image a 16GB Micro SD with 7GB of data on it, the original .img will be about 16GB but after running PiShrink, it will be about 7GB. Upon the first boot, the image will resize the max size of the SD card, so if you restore the 7GB image to a 32GB card, it will expand to have a max of 32GB.

Shrink Raspberry Pi / RetroPie .img on Windows with WSL

  1. Move your Raspberry Pi / RetroPie .img to C:\RPI\
    1. Ex: C:\RPI\RetroPie48.img
  2. Download PiShrink.sh and move it to C:\RPI\
  3. Open your WLS Linux Distro (ie: Ubuntu) and run the below commands:
    1. The time to complete will vary based on the size of the image and how much it can shrink. It took about 20min for me to shrink a 120GB image to 22GB.
cd /mnt/c/rpi
sudo ./pishrink.sh retropie48.img
Oct 13

Install Linux on Windows with WSL

Install a Linux distribution using the Windows Subsystem for Linux (WSL), which enables you to use Linux tools on you Windows File System.

Install WSL | Microsoft Learn

Prerequisites

  • Windows 10 version 2004 or higher (Build 19041 or higher)
  • Windows 11

Install WSL Command

From an elevated PowerShell or Command Prompt run the below command and you will need to reboot once complete. The command only works if WSL is not installed (See next section to determine WSL version & installed Linux distros), and by default will install Ubuntu (This can be change by specify -d <distributionname> in the command below).

wsl --install

After you reboot, the Ubuntu install will finish up and prompt you to create a username and password (Does not need to match your Windows username).

List and Install Linux Distributions

See a list of available Linux Distributions available for download through the Microsoft Online Store, run the below from an elevated PowerShell or Windows Command Prompt:

wsl --list --online

See a list of Linux Distributions installed

wsl --list -verbose

Install Linux Distribution

wsl --install -d <Distro>
wsl --install -d Debian

Ways to Run Multiple Linux Distributions with WSL

  1. Install Windows Terminal (Recommended). Open as many tabs or Window panes to quickly switch between multiple Linux Distros or other command lines (PowerShell, Command Prompt, Azure CLI, etc). This can be installed by search Windows Terminal in the Microsoft Store.
  2. Click Windows Start menu and start typing the name of the distro such as Ubuntu
  3. From PowerShell or Windows Command Prompt, you can enter the name of your installed distro such as ubuntu
  4. From PowerShell or Windows Command Prompt, open your default Linux distro by entering wsl.exe
  5. From PowerShell or Windows Command Prompt, you can use the default Linux distro

Access Linux File System from Windows

In the Windows Explorer address bar enter \\wsl$ and you will be able to access a hidden file share for your Linux Distros.

Access Windows File System from Linux

In your Linux Distro, it automatically mounts all of your Windows drives (ex: C:). You can list them by running

ls /mnt
Apr 07

L2TP Behind Double NAT

If you’ve ever received an error connecting to a L2TP endpoint from a Windows client, where the L2TP endpoint or Windows client is behind a NAT-T device (Or Double NAT), it is most likely due to the Windows client not supporting Internet Protocol security (IPsec) network address translation (NAT) Traversal (NAT-T) security associations to servers that are located behind a NAT device. In my specific case, I setup an L2TP connection on an Ubiquiti Edge Router ER-4 that was behind a double NAT on the WAN. On the Windows client, you would most likely receive an error similar to:

The network connection between your computer and the VPN server could net be established because the remote server is not responding. This could be because one of the network devices (e.g., firewalls, NAT, routers, etc.) between your computer and the remote server is not configured to allow VPN connections. Please contact your Administrator or your service provider to determine which devices may be causing the problem.

Sources

  • https://community.ui.com/questions/EdgeMax-L2TP-with-Double-NAT/e4d45aa3-bb25-4402-97ba-b3b86877499c
  • https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/configure-l2tp-ipsec-server-behind-nat-t-device
  • https://help.ui.com/hc/en-us/articles/204950294-EdgeRouter-L2TP-IPsec-VPN-Server

By default, Windows Vista and Windows Server 2008 don’t support Internet Protocol security (IPsec) network address translation (NAT) Traversal (NAT-T) security associations to servers that are located behind a NAT device. If the virtual private network (VPN) server is behind a NAT device, a Windows Vista or Windows Server 2008-based VPN client computer can’t make a Layer 2 Tunneling Protocol (L2TP)/IPsec connection to the VPN server. This scenario includes VPN servers that are running Windows Server 2008 and Windows Server 2003.

Explanation

Because of the way in which NAT devices translate network traffic, you may experience unexpected results in the following scenario:

  • You put a server behind a NAT device.
  • You use an IPsec NAT-T environment.

If you must use IPsec for communication, use public IP addresses for all servers that you can connect to from the Internet. If you must put a server behind a NAT device, and then use an IPsec NAT-T environment, you can enable communication by changing a registry value on the VPN client computer and the VPN server.

Resolution

To create and configure the AssumeUDPEncapsulationContextOnSendRule registry value, follow these steps:

  1. Log on to the Windows Vista client computer as a user who is a member of the Administrators group.
  2. Select Start > All Programs > Accessories > Run, type regedit, and then select OK. If the User Account Control dialog box is displayed on the screen and prompts you to elevate your administrator token, select Continue.
  3. Locate and then select the following registry
    1. subkey:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PolicyAgent
  4. On the Edit menu, point to New, and then select DWORD (32-bit) Value.
  5. Type AssumeUDPEncapsulationContextOnSendRule, and then press ENTER.
  6. Right-click AssumeUDPEncapsulationContextOnSendRule, and then select Modify.
  7. In the Value Data box, type one of the following values:
    • 0It’s the default value. When it’s set to 0, Windows can’t establish security associations with servers located behind NAT devices.
    • 1When it’s set to 1, Windows can establish security associations with servers that are located behind NAT devices.
    • 2When it’s set to 2, Windows can establish security associations when both the server and VPN client computer (Windows Vista or Windows Server 2008-based) are behind NAT devices.
  8. Select OK, and then exit Registry Editor.
  9. Restart the computer.
Sep 28

Find RetroPie Unscraped Data

This is intended to be used as the supplemental script (3_CheckXML.ps1) to be used in conjunction with the 1_Clean-NoIntroRoms.ps1 and 2_CompareFolderResults.ps1 scripts. This script allows you check missing scraped data in RetroPie by examining the gamelist.xml file for each game system on you RetroPie. This can save you HOURS of time manually checking for missing ROM scraped data.

#CheckXML.ps1
#5.3
# -Added Changelog in Comments
#5.2:
# -Added variables for Write-Out Colors and Updated to Write-Out v1.3
# -Missing attributes set to $true were not being detected, only worked if they had a blank value
#5.1:
# -No Changes
#5.0 
# -New Release
$global:scriptName = $PSCommandPath
$global:scriptVersion = "v5.3"
$global:GameListRoot = "\\retropie\configs\all\emulationstation\gamelists\"
$global:MultiCartExceptions = "\(([^/)]*)(DISK|Disk|disk|Reel|4B-001, 4B-009, 8B-001, Sachen|4B-007, 4B-008, 8B-004, Sachen|4B-005, 4B-006, 8B-003, Sachen|4B-002, 4B-004, 8B-002, Sachen)([^/)]*)\)"
$global:IgnoreMulticartDuplicates = $true
$global:ValidateVerbose = $false #Validate all Data & Override ValidateChild
$global:logFile = (Split-Path -Path $PSCommandPath -Parent) + "\logs\" + (Split-Path -Path $PSCommandPath.Replace(".ps1",".log") -Leaf)
$global:logOverwrite = $true
$global:ValidateChild = [ordered]@{
	"path" = $false
	"name" = $true
	"cover" = $false
	"image" = $true
	"marquee" = $false
	"video" = $false
	"rating" = $false
	"desc" = $true
	"releasedate" = $false
	"developer" = $false
	"publisher" = $false
	"genre" = $false
	"players" = $false
	"kidgame" = $false
}
$global:romNoIntroHashTable = [ordered]@{
	#RetroPieDirName = No-IntroDirName
	"atari2600" = "Atari - 2600"
	"atari5200"  = "Atari - 5200"
	"atari7800" = "Atari - 7800 [Headered]"
	"atarijaguar" = "Atari - Jaguar"
	"atarilynx" = "Atari - Lynx [Headered]"
	"atarist" = "Atari - ST"
	"coleco" = "Coleco - ColecoVision"
	"fds" = "Nintendo - Family Computer Disk System [headered]"
	"gamegear" = "Sega - Game Gear"
	"gb" = "Nintendo - Game Boy"
	"gba" = "Nintendo - Game Boy Advance"
	"gbc" = "Nintendo - Game Boy Color"
	"mastersystem" = "Sega - Master System - Mark III"
	"megadrive" = "Sega - Mega Drive - Genesis"
	"msx" = "Microsoft - MSX"
	"n64" = "Nintendo - Nintendo 64"
	"nes" = "Nintendo - Nintendo Entertainment System [headered]"
	"ngp" = "SNK - Neo Geo Pocket"
	"ngpc" = "SNK - Neo Geo Pocket Color"
	"pcengine" = "NEC - PC Engine - TurboGrafx 16"
	"sega32x" = "Sega - 32X"
	"sg-1000" = "Sega - SG-1000"
	"snes" = "Nintendo - Super Nintendo Entertainment System"
	"vectrex" = "GCE - Vectrex"
	"virtualboy" = "Nintendo - Virtual Boy"
	"wonderswan" = "Bandai - WonderSwan"
	"wonderswancolor" = "Bandai - WonderSwan Color"
}
#Output Color Variables
$global:colorMain = "Green"
$global:colorSub1 = "DarkGreen"
$global:colorSub2 = "Cyan"
$global:colorSub3 = "DarkRed"
$global:colorOk = "DarkCyan"
$global:colorWarn = "Yellow"
$global:colorError = "Red"



Function Main()
{
	#Output Color Variables
	$global:colorMain = "Green"
	$global:colorSub1 = "DarkGreen"
	$global:colorSub2 = "Cyan"
	$global:colorOk = "DarkCyan"
	$global:colorWarn = "Yellow"
	$global:colorError = "Red"

	#Powershell 4 or greater is required
	If (!($PSVersionTable.PSVersion.Major -ge 4) -OR ($PSVersionTable.PSVersion -eq $null))
	{
		Write-Host -ForegroundColor $global:colorError "Powershell v4 or greater is required, quitting script!"
		Exit
	}

	#Create Log File
	If ($global:logOverwrite -eq $true)
	{
		Write-Out -NoNewLine -LogFile $global:logFile -Overwrite
	}
	Else 
	{
		Write-Out -NoNewLine -LogFile $global:logFile
	}
	
	#Array to Track Results For Each Game System
	$gameArray = @()

	#Check XML Gamelists for Missing Scraped Metadata
	$tStart = (Get-Date)
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	Write-Out -Text "INITIALIZE" -ForegroundColor $global:colorMain -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Script Name                 : $($global:scriptName)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Version:                    : $($global:scriptVersion)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Game List Root              : $($global:GameListRoot)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Multicart Exceptions        : $($global:MultiCartExceptions)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Ignore Multicart Exceptions : $($global:IgnoreMulticartDuplicates)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Validate Verbose            : $($global:ValidateVerbose)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Log File                    : $($global:logFile)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Log Overwrite               : $($global:logOverwrite)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Start Time                  : $($tStart)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -PostLine 1 -LogFile $global:logFile
	ForEach ($GameSystem in $global:romNoIntroHashTable.keys)
	{
		#Create Object to Store Info For Each Game System
		$gameObject = New-Object -TypeName psobject 
		$gameObject | Add-Member -MemberType NoteProperty -Name GameSystem -Value $GameSystem
		$gameObject | Add-Member -MemberType NoteProperty -Name GameList -Value "$($global:GameListRoot)$($GameSystem)\gamelist.xml"
		$gameObject | Add-Member -MemberType NoteProperty -Name GameListExist -Value $false
		$gameObject | Add-Member -MemberType NoteProperty -Name Games -Value @{}
		$gameObject | Add-Member -MemberType NoteProperty -Name RomsUnscraped -Value 0
		$gameObject | Add-Member -MemberType NoteProperty -Name MissingMetadata -Value 0
		$gameObject | Add-Member -MemberType NoteProperty -Name DuplicateRoms -Value 0

		Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
		Write-Out -Text "PROCESSING: $($gameObject.GameList)" -ForegroundColor $global:colorMain -LogFile $global:logFile
		Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile

		Write-Out -Text "STEP 1/3 - Confirm XML Exists" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
		Write-Out -Text "=====================================" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
		If (Test-Path -LiteralPath $gameObject.GameList -PathType Leaf)
		{
			$gameObject.GameListExist = $true
			Write-Out -Text "Exists - Continue" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
			
			#Read GameList
			[XML]$gameXML = Get-Content $gameObject.GameList

			Write-Out -Text "STEP 2/3 - Validate Game Details" -ForegroundColor $global:colorSub1 -PreLine 1 -LogFile $global:logFile
			Write-Out -Text "=====================================" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
			
			#Loop Each Game in the XML File
			ForEach($gameMetadata in $gameXML.gameList.game)
			{
				$RomScraped = $true
				#Output Validation
				If ($global:ValidateVerbose -eq $true)
				{
					#Validate All Attributes
					Write-Out -Text "$($gameMetadata.path)" -ForegroundColor $global:colorSub2 -LogFile $global:logFile
					Write-Out -Text "-------------------------------------" -ForegroundColor $global:colorSub2 -LogFile $global:logFile			
					ForEach ($Child in $global:ValidateChild.keys)
					{
						If ($gameMetadata.$Child -eq "" -OR $gameMetadata.$Child -eq $null)
						{
							$gameObject.MissingMetadata++
							$RomScraped = $false
							Write-Out -Text "$($Child): ","MISSING" -ForegroundColor $global:colorSub2,$global:colorError -LogFile $global:logFile
						}
						Else
						{
							Write-Out -Text "$($Child): ","$($gameMetadata.$Child)" -ForegroundColor $global:colorSub2,$global:colorOk -LogFile $global:logFile
						}
					}
					Write-Out -LogFile $global:logFile
				}
				Else
				{
					#Validate Only True Attributes
					$MetadataErrors = @()
					ForEach ($Child in $global:ValidateChild.keys)
					{
						If ($global:ValidateChild[$Child] -eq $true)
						{
							If ($gameMetadata.$Child -eq "" -OR $gameMetadata.$Child -eq $null)
							{
								$gameObject.MissingMetadata++
								$RomScraped = $false
								$MetadataErrors += $Child
							}
						}
					}
					If ($MetadataErrors.Count -gt 0)
					{
						Write-Out -Text "$($gameMetadata.path)" -ForegroundColor $global:colorSub2 -LogFile $global:logFile
						Write-Out -Text "-------------------------------------" -ForegroundColor $global:colorSub2 -LogFile $global:logFile
						ForEach ($Error in $MetadataErrors)
						{
							Write-Out -Text "$($Error): ","MISSING" -ForegroundColor $global:colorSub2,$global:colorError -LogFile $global:logFile
						}
						Write-Out -LogFile $global:logFile
					}
				}
				If ($RomScraped -eq $false)
				{
					$gameObject.RomsUnscraped++
				}

				#Add Games ROM File & Name to HashTable To Check For Duplicates
				$gameObject.Games.add($gameMetadata.path,$gameMetadata.name)
			}
			If ($gameObject.MissingMetadata -eq 0)
			{
				Write-Out -Text "Validation - Successful with $($gameObject.MissingMetadata) Errors" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
			}
			Else
			{
				Write-Out -Text "Validation - ","Failed with Unscraped ROMs: $($gameObject.RomsUnscraped) and Missing Metadata: $($gameObject.MissingMetadata)" -ForegroundColor $global:colorSub1,$global:colorError -LogFile $global:logFile
			}

			#Check For Duplicates in XML Gamelists
			Write-Out -Text "STEP 3/3 - Check For Duplicates" -ForegroundColor $global:colorSub1 -PreLine 1 -LogFile $global:logFile
			Write-Out -Text "=====================================" -ForegroundColor $global:colorSub1 -LogFile $global:logFile			
			#Get List of Unique ROM Base Names
			$colUniqueRoms = $gameObject.Games.Keys | ForEach {$gameObject.Games[$_].Split("(")[0]}
			$colUniqueRoms = $colUniqueRoms | Sort-Object -Unique
			Write-Out -Text "Unique Names: $($colUniqueRoms.Count) " -ForegroundColor $global:colorSub1 -LogFile $global:logFile
			Write-Out -Text "Total ROMs: $($gameObject.Games.Count) " -ForegroundColor $global:colorSub1 -LogFile $global:logFile			
			If ($gameObject.Games.Count -gt $colUniqueRoms.Count)
			{
				Write-Out -Text "Potential Duplicates: $($gameObject.Games.Count - $colUniqueRoms.Count)" -ForegroundColor $global:colorWarn -LogFile $global:logFile
			}
			Else 
			{
				Write-Out -Text "Potential Duplicates: $($gameObject.Games.Count - $colUniqueRoms.Count)" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
			}
			
			#Find Duplicates
			ForEach ($uniqueRom in $colUniqueRoms)
			{
				#Get HashTable Key for Each Duplicate
				If ($global:IgnoreMulticartDuplicates)
				{
					$Duplicates = $gameObject.Games.Keys | ? { $gameObject.Games[$_] -match ("^"+[regex]::escape($uniqueRom)+"\(") -AND $gameObject.Games[$_] -cnotmatch $global:MultiCartExceptions }
				}
				Else 
				{
					#Ensure Match to First '(' Incase of Common Name (ex: 'Megaman' vs 'Megaman 2')
					$Duplicates = $gameObject.Games.Keys | ? { $gameObject.Games[$_] -match ("^"+[regex]::escape($uniqueRom)+"\(") }
				}
				If ($Duplicates.Count -gt 1)
				{
					
					$gameObject.DuplicateRoms+= ($Duplicates.Count - 1)
					Write-Out -Text "Duplicate($($Duplicates.Count - 1)): $($uniqueRom)" -ForegroundColor $global:colorSub2 -PreLine 1 -LogFile $global:logFile
					Write-Out -Text "----------------------------------------" -ForegroundColor $global:colorSub2 -LogFile $global:logFile					
					ForEach ($Dup in $Duplicates)
					{
						Write-Out -Text "ROM: ","$($Dup )" -ForegroundColor $global:colorSub2,$global:colorError -LogFile $global:logFile
					}
				}
			}
			If ($gameObject.DuplicateRoms -eq 0)
			{
				Write-Out -Text "Duplicates: None Found" -ForegroundColor $global:colorSub1 -PreLine 1 -PostLine 1 -LogFile $global:logFile
			}
			Else 
			{
				Write-Out -Text "Duplicates: ","$($gameObject.DuplicateRoms) Found" -ForegroundColor $global:colorSub1,$global:colorError -PreLine 1 -PostLine 1 -LogFile $global:logFile
			}
		}
		Else
		{
			Write-Out -Text "Not Exist - Skip" -ForegroundColor $global:colorError -PostLine 1  -LogFile $global:logFile
		}
		
		#Add Game System to Array to Track Results 
		$gameArray += $gameObject
	}

	#Display Final Results 
	Write-Out -Text "*****************************************************************" -ForegroundColor $global:colorMain -PreLine 1 -LogFile $global:logFile
	Write-Out -Text "END RESULTS" -ForegroundColor $global:colorMain -LogFile $global:logFile
	Write-Out -Text "*****************************************************************" -ForegroundColor $global:colorMain -LogFile $global:logFile
	ForEach ($game in $gameArray)
	{
		Write-Out -Text "* $($game.GameSystem):" -ForegroundColor $global:colorMain -LogFile $global:logFile
		If ($game.GameListExist -eq $false)
		{
			Write-Out -Text "Error ($($game.GameList)) Does Not Exist" -ForegroundColor $global:colorError -LogFile $global:logFile
		}
		Else 
		{
			Write-Out -Text "* -Total Games:      $($game.Games.Count)" -ForegroundColor $global:colorMain -LogFile $global:logFile
			If ($game.RomsUnscraped -eq 0)
			{
				Write-Out -Text "* -ROMs Not Scraped: $($game.RomsUnscraped)" -ForegroundColor $global:colorMain -LogFile $global:logFile
			}
			Else 
			{
				Write-Out -Text "* -ROMs Not Scraped: ","$($game.RomsUnscraped)" -ForegroundColor $global:colorMain,$global:colorError -LogFile $global:logFile
			}
			If ($game.MissingMetadata -eq 0)
			{
				Write-Out -Text "* -Missing Metadata: $($game.MissingMetadata)" -ForegroundColor $global:colorMain -LogFile $global:logFile
			}
			Else 
			{
				Write-Out -Text "* -Missing Metadata: ","$($game.MissingMetadata)" -ForegroundColor $global:colorMain,$global:colorError -LogFile $global:logFile
			}
			If ($game.DuplicateRoms -eq 0)
			{
				Write-Out -Text "* -Duplicate ROMs:   $($game.DuplicateRoms)" -ForegroundColor $global:colorMain -LogFile $global:logFile
			}
			Else 
			{
				Write-Out -Text "* -Duplicate ROMs:   ","$($game.DuplicateRoms)" -ForegroundColor $global:colorMain,$global:colorError -LogFile $global:logFile
			}
		}
		Write-Out -ForegroundColor $global:colorMain -Text "* "-LogFile $global:logFile
	}
	$tEnd = (Get-Date)
	Write-Out -ForegroundColor $global:colorMain -Text "* Start Time              : $($tStart)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* End Time                : $($tEnd)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Elapsed Time            : $([math]::Round(($tEnd-$tStart).totalseconds, 2)) seconds" -LogFile $global:logFile	
	Write-Out -Text "*****************************************************************" -ForegroundColor $global:colorMain -LogFile $global:logFile
}



Function Write-Out
{
	<#
	.SYNOPSIS
	Write output to console and/or log file. Supports multiple foreground and background colors on a single line and 
	advanced options to insert lines, spaces, or tabs before or after the text as well as timestamps.
	.DESCRIPTION
	Uses Write-Host to output color text to the console, and supports using multiple foreground and background colors 
	on a single line. Uses Set-Content and Add-Content to output to a file.
	
	It works by accepting strings as an array and then you can assign the foreground or background colors by passing
	them as arrays for the matching text. It also accepts Default which allows you skip specifying a color and use the 
	default.
	
	This function requires Powershell 4.0 to support the alias attribute in the function.
	.PARAMETER Text
	Optional. Array of strings to write to console/log. If you do not need to use multiple colors on a single line you can pass a single string instead of an array of strings. Otherwise, if you need to multiple colors on a single line pass an array of strings and associated colors using -ForegroundColor, -BackgroundColor, -ForegroundColorDefault, or -BackgroundColorDefault.
	.PARAMETER ForegroundColor
	Optional. Array of foreground Colors to write to console. Default means the ForegroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER ForegroundColorDefault
	Optional. Default foreground color to write to console. Default is set to white.
	Valid Colors: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColor
	Optional. Array of background colors to write to console. Default means the BackgroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColorDefault
	Optional. Default background color to write to console. Default is none.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER PreLine
	Optional. Add blank lines before your text. Default is 0.
	.PARAMETER PreSpace
	Optional. Add spaces before your text. Default is 0.
	.PARAMETER PreTab
	Optional. Add Tabs before your text. Default is 0.
	.PARAMETER TimeStampFormat
	Optional. Format used for time stamps. Default is 'yyyy-MM-dd HH:mm:ss'
	.PARAMETER TimeStamp
	Optional. Adds time stamp in square brackets before your text.
	.PARAMETER ClearHost
	Optional. Clear the console before your text.
	.PARAMETER PostLine
	Optional. Add blank lines after your text. Default is 0.
	.PARAMETER PostSpace
	Optional. Add spaces after your text. Default is 0.
	.PARAMETER PostTab
	Optional. Add tabs after your text. Default is 0.
	.PARAMETER NoNewLine
	Optional. Do not add a new line after your text and optional post text options. Default is to add a new line.
	.PARAMETER LogFile
	Optional. Absolute or relative path of the log file.
	.PARAMETER Overwrite
	Optional. Ovewrite the log file. Default is to append to the log file.
	.INPUTS
	Parameters above
	.OUTPUTS
	None
	.NOTES
	Author: Brian Steinmeyer
	URL: http://sigkillit.com/
	Created: 4/18/2020
	Version 1.3
	- Added LogOnly Option for only writing text to a log file
	Version 1.2
	- Set Text, ForegroundColor, and BackgroundColor to default value of @() to fix errors checking counts in some circumstances
	- Fixed an issue where ForegroundColorDefault and BackgroundColorDefault were not working properly in some circumstances
	- Added Requires -Version 4.0
	Version 1.1
	- Completely rewrote the "Main Text" section
	- Added "Default" as a color option, which allows you to use the default values for foreground/background 
	  - Useful when you want to specify a backgroundcolor in certain parts of a line like the middle
	  - Ex: Write-Out -Text "How to ","highlight ","the middle text" -BackgroundColor Default,Yellow,Default
	Version: 1.0
	- Initial Creation inspired by PSWriteColor (https://github.com/EvotecIT/PSWriteColor)
	  - Improved upon by switching Foreground and Background Colors to default values if colors are not specifid for all strings.
	    Will also ignore extra colors if more colors are specified than strings specified.
	.EXAMPLE
	Write-Out -Text "Start with Red Text ","Then Switch to Blue Text ","Now Magenta" -ForegroundColor Red,Blue,Magenta 
	.EXAMPLE
	Write-Out -Text "White on Black ","Black on White ","Dark Cyan on Cyan ","Yellow on Green ","Default Color" -ForegroundColor White,Black,DarkCyan,Yellow -BackgroundColor Black,White,Cyan,Green
	.EXAMPLE
	Write-Out -Text "Make this"," entire line"," the same color by setting defaults" -ForegroundColorDefault Yellow -BackgroundColorDefault Magenta
	.EXAMPLE
	Write-Out -Text "Add a blank line and two tabs ","before ","my text" -ForegroundColor Green,Cyan,White -PreLine 1 -PreTab 2
	.EXAMPLE
	Write-Out -Text "Add two blank ","lines ","after my text" -ForegroundColor White,Green,White -PostLine 2
	.EXAMPLE
	Write-Out -Text "Add 3 spaces before my text" -ForegroundColor Gray -Prespace 3
	.EXAMPLE
	Write-Out -Text "White text and a tab after" -ForegroundColor White -NoNewLine -PostTab 1
	Write-Out -Text "Black text on Yellow ","and then back to white" -ForegroundColor Black,White -BackgroundColor Yellow
	.EXAMPLE
	Write-Out -Text "An easy way to ","highlight ","text in the middle" -BackgroundColor Default,Yellow
	.EXAMPLE
	Write-Out -Text "You can even add a ","time stamp ","before your output" -ForegroundColor White,Green,White -TimeStamp -PreLine 3
	.EXAMPLE
	Write-Out -Text "You can change the ","time stamp format" -ForegroundColor White,Yellow -TimeStamp -TimeStampFormat "dd-MM-yyy HH:mm" -PreLine 1 -PostLine 1
	.EXAMPLE
	Write-Out -Text "An"," Error"," occurred let's write overwrite/create a new log file" -ForegroundColor White,Red,White -TimeStamp -LogFile "script.log" -Overwrite
	.EXAMPLE
	Write-Out -Text "Now you can ","Append ","this line to your log file" -ForegroundColor Cyan,Magenta -TimeStamp -LogFile "script.log"
	#>
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory=$false)]
		[AllowEmptyString()]
        [alias ('T')]
		[String[]]
		$Text = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGC', 'FC')]
		[string[]]
		$ForegroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGCD', 'FCD')]
		[string]
		$ForegroundColorDefault = [ConsoleColor]::White,
		[Parameter(Mandatory=$false)]
        [ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGC', 'BC')]
		[String[]]
		$BackgroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGCD', 'BCD')]
		[string]
		$BackgroundColorDefault = "Default",
		[Parameter(Mandatory=$false)]
		[int]
		$PreLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreTab = 0,
		[Parameter(Mandatory=$false)]
		[Alias('TSF', 'TS')]
		[string]
		$TimeStampFormat = 'yyyy-MM-dd HH:mm:ss',
		[Parameter(Mandatory=$false)]
		[switch]
		$TimeStamp,		
		[Parameter(Mandatory=$false)]
		[switch]
		$ClearHost,
		[Parameter(Mandatory=$false)]
		[int]
		$PostLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostTab = 0,
		[Parameter(Mandatory=$false)]
		[switch]
		$NoNewLine = $false,
		[Parameter(Mandatory=$false)]
		[alias ('Log', 'L')]
		[string]
		$LogFile = '',
		[Parameter(Mandatory=$false)]
		[switch]
		$LogOnly = $false,
		[Parameter(Mandatory=$false)]
		[switch]
		$Overwrite = $false
	)
	
	Begin
	{
		#Nothing to Do Here
	}
	
	Process
	{
		#Optional - Do Not Write to ConsoleColor
		If (!($LogOnly))
		{
			#Optional - Prefix Text 
			If ($ClearHost) { Clear-Host }
			If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) before Main text
			If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) before Main text
			If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) before Main text
			If ($TimeStamp) { Write-Host -Object "[$([datetime]::Now.ToString($TimeStampFormat))]" -NoNewline } # Add Timestamp before Main Text

			#MAIN TEXT
			If (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0))
			{
				#Text Only Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				} 
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -eq 0))
			{ 
				#Text and ForegroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -gt 0))
			{
				#Text and BackgroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
						}
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -gt 0))
			{ 
				#Text, ForegroundColor, and BackgroundColor Specified (FAILS NOT WRITING DEFAULT)
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
						}					
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			Else
			{
				#No Text, ForegroundColor, or BackgroundColor Specified ($Text.Count -eq 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0)
			}
				
			#Post Text
			If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) after Main text
			If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) after Main text
			If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) after Main text
			If ($NoNewLine) { Write-Host -NoNewline } else { Write-Host } # Add New Line after Main Text Unless NoNewLine is Supplied
		}
		
		#Log File
		$TextToWrite = "" #Build Text to Write to Log File
		If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) before Main text
		If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) before Main text
		If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) before Main text
		If ($TimeStamp) { $TextToWrite += "[$([datetime]::Now.ToString($TimeStampFormat))]" } # Add Timestamp before Main Text
		If ($Text.Count -gt 0) { For ($i = 0; $i -lt $Text.Count; $i++) { $TextToWrite += $Text[$i] } } #Add Main Text
		If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) after Main text
		If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) after Main text
		If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) after Main text		
		If ($LogFile -eq "")
		{
			#No LogFile Specified - Skip
		}
		ElseIf(!(Test-Path -Path $LogFile))
		{
			#Create Log File If Does Not Exist
			If ($NoNewLine)
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite
			}
			Else 
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite`r`n
			}
		}
		Else
		{
			#Log File Exists			
			If($Overwrite)
			{
				#Overwrite Log File
				If ($NoNewLine)
				{
					Set-Content -Path $LogFile -Value $TextToWrite -NoNewline
				}
				Else 
				{
					Set-Content -Path $LogFile -Value $TextToWrite
				}
			}
			Else
			{
				#Append Log File
				Try {
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop
					}
				} Catch {
					Start-Sleep -s 3
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite
					}
				}
			}
		}
	}
	
	End
	{
		#Nothing to Do Here
	}
}



Main
Sep 28

Compare RetroPie ROM Folders

This is intended to be used as the supplemental script (2_CompareFolderResults.ps1) to be used in conjunction with the 1_Clean-NoIntroRoms.ps1 and 3_CheckXML.ps1 scripts. This script allows you to compare the files/roms in 2 directories to show the differences in files of each folder by using the compare-object cmdlet. It is intended to compare the different results in 1_Clean-NoIntroRoms.ps1 when you change the filters for a system.

2_CompareFolderResults.ps1

See supplemental scripts:

  • 1_Clean-NoIntroRoms.ps1 – Clean No-Intro 2018 ROMs by eliminating duplicates and keeping 1 x ROM per Game by using filters for preference
  • 3_CheckXML.ps1 – Find missing scraped data in RetroPie by examining the gamelist.xml file for each game system on you RetroPie

Global Variable Definitions:

  • $global:scriptName
    • Name of the Script, defaults to full name of the script
  • $global:scriptVersion
    • Script Version
  • $global:romsPath
    • Initial root folder containing ROMs for each No-Intro Game System, equivalent to “roms” directory on RetroPie
  • $global:roms1Path
    • Secondary root folder containing ROMs for each No-Intro Game System to compare to initial root folder. If you want to test modifying filters on 1_Clean-NoIntroRoms.ps1, you can use this directory to compare your results
  • $global:logFile
    • Log file output of the entire run. Default is a “logs” directory in the same directory as the script with a log name that matches the script’s base name. Ex: .\logs\1_Clean-NoIntroRom.log
  • $global:logOverwrite
    • Overwrite the log file. Default is $true
  • $global:romNoIntroHashTable
    • Hashtable setting each game systems directory name on RetroPie and in the No-Intro 2018 complete ROM set. Default directory names are used for each. Uses the No-Intro Directory Name for comparing game systems in $global:romsPath and $global:roms1Path
#CompareFolderResults.ps1
#5.3
# -Added Changelog in Comments
#5.2
# -Added variables for Write-Out Colors and Updated to Write-Out v1.3
#5.1
# -No Changes
#5.0 
# -New Release
$global:scriptName = $PSCommandPath
$global:scriptVersion = "v5.3"
$global:romsPath = "C:\Games\RetroPie\roms\"
$global:roms1Path = "C:\Games\RetroPie\roms1\"
$global:logFile = (Split-Path -Path $PSCommandPath -Parent) + "\logs\" + (Split-Path -Path $PSCommandPath.Replace(".ps1",".log") -Leaf)
$global:logOverwrite = $true
$global:romNoIntroHashTable = [ordered]@{
	#RetroPieDirName = No-IntroDirName
	"atari2600" = "Atari - 2600" 
	"atari5200"  = "Atari - 5200"
	"atari7800" = "Atari - 7800 [Headered]"
	"atarilynx" = "Atari - Lynx [Headered]"
	"atarijaguar" = "Atari - Jaguar"
	"atarist" = "Atari - ST"
	"coleco" = "Coleco - ColecoVision"
	"fds" = "Nintendo - Family Computer Disk System [headered]"
	"gamegear" = "Sega - Game Gear"
	"gb" = "Nintendo - Game Boy"
	"gba" = "Nintendo - Game Boy Advance"
	"gbc" = "Nintendo - Game Boy Color"
	"mastersystem" = "Sega - Master System - Mark III"
	"megadrive" = "Sega - Mega Drive - Genesis"
	"msx" = "Microsoft - MSX"
	"n64" = "Nintendo - Nintendo 64"
	"nes" = "Nintendo - Nintendo Entertainment System [headered]"
	"ngp" = "SNK - Neo Geo Pocket"
	"ngpc" = "SNK - Neo Geo Pocket Color"
	"pcengine" = "NEC - PC Engine - TurboGrafx 16"
	"sega32x" = "Sega - 32X"
	"sg-1000" = "Sega - SG-1000"
	"snes" = "Nintendo - Super Nintendo Entertainment System"
	"vectrex" = "GCE - Vectrex"
	"virtualboy" = "Nintendo - Virtual Boy"
	"wonderswan" = "Bandai - WonderSwan"
	"wonderswancolor" = "Bandai - WonderSwan Color"
}
#Output Color Variables
$global:colorMain = "Green"
$global:colorSub1 = "Green"
$global:colorSub2 = "DarkGreen"
$global:colorSub3 = "DarkRed"
$global:colorOk = "Green"
$global:colorWarn = "Yellow"
$global:colorError = "Red"
	
	
	
Function Main()
{
	#Powershell 4 or greater is required
	If (!($PSVersionTable.PSVersion.Major -ge 4) -OR ($PSVersionTable.PSVersion -eq $null))
	{
		Write-Host -ForegroundColor $global:colorError "Powershell v4 or greater is required, quitting script!"
		Exit
	}
	
	#Create Log File
	If ($global:logOverwrite -eq $true)
	{
		Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile -Overwrite
	}
	Else 
	{
		Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	}
	Write-Out -ForegroundColor $global:colorMain -Text "INITIALIZE" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Script Name             : $global:scriptName" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Version:                : $global:scriptVersion" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* ROM Dir                 : $($global:romsPath)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* ROM Dir1                : $($global:roms1Path)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Log File                : $($global:logFile)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Log Overwrite           : $($global:logOverwrite)" -LogFile $global:logFile
	$tStart = (Get-Date)
	Write-Out -ForegroundColor $global:colorMain -Text "* Start Time              : $($tStart)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	
	$diffCount = 0
	ForEach ($key in $global:romNoIntroHashTable.keys)
	{
		Write-Out -PreLine 1 -Text "PROCCESSING ($($key)): $($global:romsPath | Split-Path -Leaf) :: $($global:roms1Path | Split-Path -Leaf)" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
		Write-Out -Text "----------------------------------------------------------" -ForegroundColor $global:colorSub1 -LogFile $global:logFile
		If ((Test-Path -LiteralPath "$($global:romsPath)$($key)" -PathType Container) -AND (Test-Path -LiteralPath "$($global:roms1Path)$($key)" -PathType Container))
		{
			$fso = Get-ChildItem -Recurse -File -Path "$($global:romsPath)$($key)"
			$fso1 = Get-ChildItem -Recurse -File -Path "$($global:roms1Path)$($key)"
			If ($fso.Count -gt 0 -AND $fso1.Count -gt 0)
			{
				$results = Compare-Object -ReferenceObject $fso -DifferenceObject $fso1
				$diffFound = $false
				ForEach ($r in $results)
				{
					If ($r.SideIndicator -eq "<=")
					{
						$diffFound = $true
						$diffCount++
						Write-Out -Text "$($r.SideIndicator) $($r.InputObject)" -ForegroundColor $global:colorSub2 -LogFile $global:logFile
					}
					ElseIf ($r.SideIndicator -eq "=>")
					{
						$diffFound = $true
						$diffCount++
						Write-Out -Text "$($r.SideIndicator) $($r.InputObject)" -ForegroundColor $global:colorSub3 -LogFile $global:logFile
					}
					Else
					{
						#Not Different
					}
				}
				If ($diffFound -eq $false)
				{
					Write-Out -Text "No Differences Found" -ForegroundColor $global:colorSub2 -LogFile $global:logFile
				}
			}
			Else
			{
				Write-Out -Text "ROM Folder(s) Do Not Have ROMs" -ForegroundColor $global:colorError -LogFile $global:logFile
			}
		}
		Else
		{
			Write-Out -Text "ROM Folder(s) Do Not Exist" -ForegroundColor $global:colorError -LogFile $global:logFile
		}
	}
	
	#Results
	Write-Out -PreLine 1 -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "END RESULTS" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile
	$tEnd = (Get-Date)
	Write-Out -ForegroundColor $global:colorMain -Text "* Differences Found       : $($diffCount)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Start Time              : $($tStart)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* End Time                : $($tEnd)" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "* Elapsed Time            : $([math]::Round(($tEnd-$tStart).totalseconds, 2)) seconds" -LogFile $global:logFile
	Write-Out -ForegroundColor $global:colorMain -Text "****************************************************************" -LogFile $global:logFile	
}



Function Write-Out
{
	<#
	.SYNOPSIS
	Write output to console and/or log file. Supports multiple foreground and background colors on a single line and 
	advanced options to insert lines, spaces, or tabs before or after the text as well as timestamps.
	.DESCRIPTION
	Uses Write-Host to output color text to the console, and supports using multiple foreground and background colors 
	on a single line. Uses Set-Content and Add-Content to output to a file.
	
	It works by accepting strings as an array and then you can assign the foreground or background colors by passing
	them as arrays for the matching text. It also accepts Default which allows you skip specifying a color and use the 
	default.
	
	This function requires Powershell 4.0 to support the alias attribute in the function.
	.PARAMETER Text
	Optional. Array of strings to write to console/log. If you do not need to use multiple colors on a single line you can pass a single string instead of an array of strings. Otherwise, if you need to multiple colors on a single line pass an array of strings and associated colors using -ForegroundColor, -BackgroundColor, -ForegroundColorDefault, or -BackgroundColorDefault.
	.PARAMETER ForegroundColor
	Optional. Array of foreground Colors to write to console. Default means the ForegroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER ForegroundColorDefault
	Optional. Default foreground color to write to console. Default is set to white.
	Valid Colors: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColor
	Optional. Array of background colors to write to console. Default means the BackgroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColorDefault
	Optional. Default background color to write to console. Default is none.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER PreLine
	Optional. Add blank lines before your text. Default is 0.
	.PARAMETER PreSpace
	Optional. Add spaces before your text. Default is 0.
	.PARAMETER PreTab
	Optional. Add Tabs before your text. Default is 0.
	.PARAMETER TimeStampFormat
	Optional. Format used for time stamps. Default is 'yyyy-MM-dd HH:mm:ss'
	.PARAMETER TimeStamp
	Optional. Adds time stamp in square brackets before your text.
	.PARAMETER ClearHost
	Optional. Clear the console before your text.
	.PARAMETER PostLine
	Optional. Add blank lines after your text. Default is 0.
	.PARAMETER PostSpace
	Optional. Add spaces after your text. Default is 0.
	.PARAMETER PostTab
	Optional. Add tabs after your text. Default is 0.
	.PARAMETER NoNewLine
	Optional. Do not add a new line after your text and optional post text options. Default is to add a new line.
	.PARAMETER LogFile
	Optional. Absolute or relative path of the log file.
	.PARAMETER Overwrite
	Optional. Ovewrite the log file. Default is to append to the log file.
	.INPUTS
	Parameters above
	.OUTPUTS
	None
	.NOTES
	Author: Brian Steinmeyer
	URL: http://sigkillit.com/
	Created: 4/18/2020
	Version 1.3
	- Added LogOnly Option for only writing text to a log file
	Version 1.2
	- Set Text, ForegroundColor, and BackgroundColor to default value of @() to fix errors checking counts in some circumstances
	- Fixed an issue where ForegroundColorDefault and BackgroundColorDefault were not working properly in some circumstances
	- Added Requires -Version 4.0
	Version 1.1
	- Completely rewrote the "Main Text" section
	- Added "Default" as a color option, which allows you to use the default values for foreground/background 
	  - Useful when you want to specify a backgroundcolor in certain parts of a line like the middle
	  - Ex: Write-Out -Text "How to ","highlight ","the middle text" -BackgroundColor Default,Yellow,Default
	Version: 1.0
	- Initial Creation inspired by PSWriteColor (https://github.com/EvotecIT/PSWriteColor)
	  - Improved upon by switching Foreground and Background Colors to default values if colors are not specifid for all strings.
	    Will also ignore extra colors if more colors are specified than strings specified.
	.EXAMPLE
	Write-Out -Text "Start with Red Text ","Then Switch to Blue Text ","Now Magenta" -ForegroundColor Red,Blue,Magenta 
	.EXAMPLE
	Write-Out -Text "White on Black ","Black on White ","Dark Cyan on Cyan ","Yellow on Green ","Default Color" -ForegroundColor White,Black,DarkCyan,Yellow -BackgroundColor Black,White,Cyan,Green
	.EXAMPLE
	Write-Out -Text "Make this"," entire line"," the same color by setting defaults" -ForegroundColorDefault Yellow -BackgroundColorDefault Magenta
	.EXAMPLE
	Write-Out -Text "Add a blank line and two tabs ","before ","my text" -ForegroundColor Green,Cyan,White -PreLine 1 -PreTab 2
	.EXAMPLE
	Write-Out -Text "Add two blank ","lines ","after my text" -ForegroundColor White,Green,White -PostLine 2
	.EXAMPLE
	Write-Out -Text "Add 3 spaces before my text" -ForegroundColor Gray -Prespace 3
	.EXAMPLE
	Write-Out -Text "White text and a tab after" -ForegroundColor White -NoNewLine -PostTab 1
	Write-Out -Text "Black text on Yellow ","and then back to white" -ForegroundColor Black,White -BackgroundColor Yellow
	.EXAMPLE
	Write-Out -Text "An easy way to ","highlight ","text in the middle" -BackgroundColor Default,Yellow
	.EXAMPLE
	Write-Out -Text "You can even add a ","time stamp ","before your output" -ForegroundColor White,Green,White -TimeStamp -PreLine 3
	.EXAMPLE
	Write-Out -Text "You can change the ","time stamp format" -ForegroundColor White,Yellow -TimeStamp -TimeStampFormat "dd-MM-yyy HH:mm" -PreLine 1 -PostLine 1
	.EXAMPLE
	Write-Out -Text "An"," Error"," occurred let's write overwrite/create a new log file" -ForegroundColor White,Red,White -TimeStamp -LogFile "script.log" -Overwrite
	.EXAMPLE
	Write-Out -Text "Now you can ","Append ","this line to your log file" -ForegroundColor Cyan,Magenta -TimeStamp -LogFile "script.log"
	#>
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory=$false)]
		[AllowEmptyString()]
        [alias ('T')]
		[String[]]
		$Text = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGC', 'FC')]
		[string[]]
		$ForegroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGCD', 'FCD')]
		[string]
		$ForegroundColorDefault = [ConsoleColor]::White,
		[Parameter(Mandatory=$false)]
        [ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGC', 'BC')]
		[String[]]
		$BackgroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGCD', 'BCD')]
		[string]
		$BackgroundColorDefault = "Default",
		[Parameter(Mandatory=$false)]
		[int]
		$PreLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreTab = 0,
		[Parameter(Mandatory=$false)]
		[Alias('TSF', 'TS')]
		[string]
		$TimeStampFormat = 'yyyy-MM-dd HH:mm:ss',
		[Parameter(Mandatory=$false)]
		[switch]
		$TimeStamp,		
		[Parameter(Mandatory=$false)]
		[switch]
		$ClearHost,
		[Parameter(Mandatory=$false)]
		[int]
		$PostLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostTab = 0,
		[Parameter(Mandatory=$false)]
		[switch]
		$NoNewLine = $false,
		[Parameter(Mandatory=$false)]
		[alias ('Log', 'L')]
		[string]
		$LogFile = '',
		[Parameter(Mandatory=$false)]
		[switch]
		$LogOnly = $false,
		[Parameter(Mandatory=$false)]
		[switch]
		$Overwrite = $false
	)
	
	Begin
	{
		#Nothing to Do Here
	}
	
	Process
	{
		#Optional - Do Not Write to ConsoleColor
		If (!($LogOnly))
		{
			#Optional - Prefix Text 
			If ($ClearHost) { Clear-Host }
			If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) before Main text
			If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) before Main text
			If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) before Main text
			If ($TimeStamp) { Write-Host -Object "[$([datetime]::Now.ToString($TimeStampFormat))]" -NoNewline } # Add Timestamp before Main Text

			#MAIN TEXT
			If (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0))
			{
				#Text Only Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				} 
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -eq 0))
			{ 
				#Text and ForegroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -gt 0))
			{
				#Text and BackgroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
						}
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -gt 0))
			{ 
				#Text, ForegroundColor, and BackgroundColor Specified (FAILS NOT WRITING DEFAULT)
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
						}					
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			Else
			{
				#No Text, ForegroundColor, or BackgroundColor Specified ($Text.Count -eq 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0)
			}
				
			#Post Text
			If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) after Main text
			If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) after Main text
			If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) after Main text
			If ($NoNewLine) { Write-Host -NoNewline } else { Write-Host } # Add New Line after Main Text Unless NoNewLine is Supplied
		}
		
		#Log File
		$TextToWrite = "" #Build Text to Write to Log File
		If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) before Main text
		If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) before Main text
		If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) before Main text
		If ($TimeStamp) { $TextToWrite += "[$([datetime]::Now.ToString($TimeStampFormat))]" } # Add Timestamp before Main Text
		If ($Text.Count -gt 0) { For ($i = 0; $i -lt $Text.Count; $i++) { $TextToWrite += $Text[$i] } } #Add Main Text
		If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) after Main text
		If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) after Main text
		If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) after Main text		
		If ($LogFile -eq "")
		{
			#No LogFile Specified - Skip
		}
		ElseIf(!(Test-Path -Path $LogFile))
		{
			#Create Log File If Does Not Exist
			If ($NoNewLine)
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite
			}
			Else 
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite`r`n
			}
		}
		Else
		{
			#Log File Exists			
			If($Overwrite)
			{
				#Overwrite Log File
				If ($NoNewLine)
				{
					Set-Content -Path $LogFile -Value $TextToWrite -NoNewline
				}
				Else 
				{
					Set-Content -Path $LogFile -Value $TextToWrite
				}
			}
			Else
			{
				#Append Log File
				Try {
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop
					}
				} Catch {
					Start-Sleep -s 3
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite
					}
				}
			}
		}
	}
	
	End
	{
		#Nothing to Do Here
	}
}



Main
Sep 09

RetroPie Install Guide RaspberryPi 4

This is the updated article specifically for the RetroPie on the RaspberryPi 4 with the Retroflag NESPi 4 Case. This will support up to most systems up until Playstation 1 (PS1). If you’re interested in RetroPie on the RaspberryPi 3/3B+, you can refer to my original article but please note this one has some additional information that still applies.

Retroflg NESPi 4 Case

Hardware Used

Here is the hardware and pricing on my build. You can lessen the cost by using existing hardware or replacing it with cheaper items. The important items are the Raspberry Pi 4B & RetroFlag NESPi 4 Case.

  • RaspberryPi 4 Model B 4GB RAM
    • $59 on Amazon
    • Note: No need to get 8GB as it does not affect game performance
  • SanDisk 128GB Extreme microSDXC UHS-I Memory Card
    • $24.88 on Amazon
    • Note: You can go much smaller if you plan to store ROMs on a SSD such as an 8GB card for about $8
  • RetroFlag NESPi 4 Case
    • $39.99 on Amazon
    • Exact replicate of NES, functioning power & reset buttons, removable USB 3.0 SSD HDD, and all cables moved to the back now. Retroflag makes cases for other
    • IMPORTANT NOTE: There are 2 versions of this case and the original version has issues with 2.4GHz wireless and Bluetooth when using the USB 3.0 Ports due to improper shielding!!!
      • Version 2 (RECOMMENDED): You can identify this version because it has 2 x white USB 2.0 ports on the front of the case! The changes on this case fix the 2.4GHz wireless and more importantly the Bluetooth issues!
      • Version 1 (NOT RECOMMENDED): You identify this version because it has 1 x white USB 2.0 and 1 x blue USB 3.0 port on the front of the case! If you use USB wired controllers and wired ethernet, this will not be an issue and you can use the SSD on USB 3.0. If you use 2.4GHz Wireless or Bluetooth, DO NOT USE THIS CASE! The only known workarounds are to plug the SSD into the USB 2.0 port and disconnect the USB 3.0 ports (which means the front USB ports won’t work) or shield the USB cables (I’ve heard/saw mixed results)
  • Optional: Western Digital 1TB WD Blue 3D NAND Internal PC SSD
    • $104.99 on Amazon
    • Although optional, I would recommend this to store the ROMs as USB 3.0 gives better performance for an SSD vs. the microSD card and more longevity
  • Optional: 2 x 8Bitdo Sn30 Pro+ Bluetooth Gamepad
  • Optional: Rii mini i8+ Keyboard

Software Used

Resources

Update 8Bitdo Controller Firmware

  • Download the latest 8Bitdo Controller Firmware on a Windows Computer
  • Run the update utility and follow the onscreen instructions

Create RetroPie SD Card

  • Download RetroPie imager/installer for the appropriate version of your Raspberry Pi on a Windows Computer
  • Connect the MicroSD card to your Windows Computer
  • Open the downloaded Raspberry Pi Imager software
    • It will automatically select the Operating System for downloaded version of RetroPi
    • Select your MicroSD card as the Storage device
    • Click Write to write RetroPi to the MicroSD card

Install the RaspberryPi Into the Case

  • Make sure the case is powered off
  • Install the RaspberryPi into the Retroflag case as per the directions
  • Switch the Safe Shutdown switch to ON
  • Connect the Micro HDMI cable, Ethernet Cable, keyboard USB dongle, insert the microSD card, and insert the USB-C power cable
  • NOTE: Do NOT mount the SSD Into the Cartridge Case yet, we will need to format it as exFat or FAT32 on a PC using directions later in this article!

Configure US Keyboard on First Boot

  • Make sure the keyboard dongle is connected and the keyboard is charged and powered on
  • Turn the power to “ON” on the case
  • On the Welcome Screen, hold any button on the keyboard to configure it, and configure it as follows:
    • D-PAD UP: Up Arrow
    • D-PAD DOWN: Down Arrow
    • D-PAD LEFT: Left Arrow
    • D-PAD RIGHT: Right Arrow
    • START: 1
    • SELECT: 2
    • Button A / East: Enter (Same as OK key)
    • Button B /South: Escape
    • For all other keys, hold any button to skip configuring
    • When OK is highlighted, press the Enter button
    • When prompted about not enabling a hotkey button, highlight YES and press Enter to use the default select button
  • Navigate to the RetroPie menu and press Enter on the keyboard
    • Select RASPI-CONFIG and press Enter on the keyboard
      • Select Localization Options and press Enter on the keyboard
      • Select Change Keyboard Layout and press Enter on the keyboard
      • Select Generic 105-key (Intl) PC and press Enter on the keyboard
      • For the keyboard layout, select Other and press Enter on the keyboard
        1. Select English (US) and press Enter on the keyboard
      • Select English (US) and press Enter on the keyboard
      • Select Default Keyboard Layout and press Enter on the keyboard
      • Select No Composer Key and press Enter on the keyboard
      • Select Finish and press Enter on the keyboard

Optional – Connect WiFi (Skip if using Wired LAN)

  • Navigate to the RetroPie menu and press Enter on the keyboard
  • Select RASPI-CONFIG and press Enter on the keyboard
    • Select Localization Options and press Enter on the keyboard
    • Select Change WI-FI Country and press Enter on the keyboard
    • Select United States and press Enter on the keyboard
    • Press OK on the success message
    • Select Finish and press Enter on the keyboard
  • *Select WIFI and press OK on the keyboard
    • Select Connect to WIFI network and press Enter on the keyboard
    • Select the SSID, and press Enter on the keyboard
    • Enter the SSID PSIK and press Enter on the keyboard
    • Select Exit and press Enter on the keyboard

Optional – Enable SSH (Useful later for scraping ROMs)

  • Navigate to the RetroPie menu and press Enter on the keyboard
  • Select RASPI-CONFIG and press Enter on the keyboard
    • Select Interface Options and press Enter on the keyboard
    • Select SSH and press Enter on the keyboard
    • Select Yes to enable SSH and press Enter on the keyboard
    • Press OK to confirm SSH is enabled
    • Select Finish and press Enter on the keyboard

Optional – Update RetroPie

  • Navigate to the RetroPie menu and press Enter on the keyboard
  • Select RETROPIE SETUP and press Enter on the keyboard
    • Select Update and press Enter on the keyboard
    • When prompted, select Yes and press Enter on the keyboard
    • Press OK/YES to all proceeding messages
    • Select Exit and press Enter on the keyboard
  • If Retropie Does Not Boot Emulation Station and is stuck asking for the retropie login
    • Login as pi
    • Navigate to the RetroPie menu and press Enter on the keyboard
    • Select RASPI-CONFIG and press Enter on the keyboard
    • Select Boot Options and press Enter on the keyboard
    • Select Desktop/CLI and press Enter on the keyboard
    • Select Console Autologin and press Enter on the keyboard
    • Select Finish and press Enter on the keyboard
    • Select Yes to reboot and press Enter on the keyboard
    • It should not restart and autologin to emulationstation again

Add the RetoFlag Safe Shutdown Script for Power/Reset Buttons

I personally recommend using crcerror’s forked version of the retroflag script vs. the official retroflag version of the script for the power/reset buttons. Here are the differences and instructions to install either:

  • crcerror Script
    • Button Functions:
      • Press Reset in Emulator: Exits emulator back to Emulationstation
      • Press Reset in Emulationstation: Restarts Emulationstation
      • Press Reset in Console: Restart the RaspberryPi
      • Press Power in Console, Emulationstation, or ROM Emulator: Shutdown the RaspberryPi
    • Install Directions:
      • Ensure you are connected to the internet & the keyboard is connected
      • Press F4 to enter the terminal
      • In the terminal, type the one-line command below (Case Sensitive):
        • wget -O – “https://raw.githubusercontent.com/crcerror/retroflag-picase/master/install.sh” | sudo bash
      • RetroPie will restart once complete
  • Retroflag Script
    • Button Functions:
      • Press Reset in Emulator/Emulationstation: Restart the RaspberryPi
      • Press Reset in Console: Nothing
      • Press Power in Emulator/Emulationstation: Shutdown the RaspberryPi
      • Press Power in Console: Nothing
    • Install Directions:
      • Ensure you are connected to the internet & the keyboard is connected
      • Press F4 to enter the terminal
      • In the terminal, type the one-line command below (Case Sensitive):
        • wget -O – “https://raw.githubusercontent.com/RetroFlag/retroflag-picase/master/install.sh” | sudo bash
      • RetroPie will restart once complete

Setup Bluetooth Controllers

This is written for the 8Bitdo controllers, but should be the same for any Bluetooth controller

  • Navigate to the RetroPie menu and press Enter on the keyboard
  • Select BLUETOOTH and press Enter on the keyboard
    • Make sure 8Bitdo mapping hack is set to off
    • Power on the 8Bitdo controller by holding the start button until the blue light comes on.  Once it is blinking, set it to pairing mode by holding the select button for 3 seconds and the blue light will start rapidly blinking
    • Select Register and Connect Bluetooth Device and press Enter on the keyboard
    • The controller will show up with its MAC address as 8Bitdo, select it and press Enter on the keyboard
    • Select DisplayYesNo and press Enter on the keyboard
      • Repeat for each additional controller
    • Select Setup udev rule for joypad and press Enter on the keyboard
      • Select an 8Bitdo controller and press Enter on the keyboard
      • Press Enter on the keyboard to confirm the entry was added
        • Repeat for each additional controller
    • Select Cancel and press Enter on the keyboard to return to Emulationstation
  • Press 1 on the keyboard to bring up the Main Menu
    • Select Configure Input and press Enter on the keyboard
    • When prompted to configure input, press Enter on the keyboard
    • Hold any key on the 8Bitdo SNES30 controller and configure it as follows
    • D-PAD UP: Up Arrow
    • D-PAD DOWN: Down Arrow
    • D-PAD LEFT: Left Arrow
    • D-PAD RIGHT: Right Arrow
    • START: Start
    • SELECT: Select
    • A: A
    • B: B
    • X: X
    • Y: Y
    • LEFT SHOULDER: L
    • RIGHT SHOULDER: R
    • For all others, hold any button to skip configuration (Or use the keyboard to skip down to OK)
    • When OK is highlighted, press the Enter on the keyboard
    • When prompted about not enabling a hotkey button, highlight YES and press Enter on the keyboard to use the default select button
    • Repeat for each Controller
  • Press 1 on the keyboard to bring up the Main Menu
    • Select Quit and press Enter on the keyboard
    • Select Restart System and press Enter on the keyboard
    • Select Yes to confirm the restart and press Enter on the keyboard

Install Optional Emulators

SystemOPT EmulatorNote
Apple IIlinapple
Atari SThatari
Commodor 64vice
Coleco Visionlr-blueMSXcoolcv not supported on Pi4 due to video drivers at this time, using lr-blueMSX instead
Dragon 32 / Tandy CoCoxroar
Intellivisionlr-freeintv
MSXlr-fmsxAdds the following files to BIOS folder: CARTS.SHA, CYRILLIC.FNT, DISK.ROM, FMPAC.ROM, FMPAC16.ROM, ITALIC.FNT, KANJI.ROM, MSX.ROM, MSX2.ROM, MSX2EXT.ROM, MSX2P.ROM, MSX2PEXT.ROM, MSXDOS2.ROM, PAINTER.ROM, RS232.ROM
Virtual Boylr-beetle-vb
Wonderswan / Wonderswan Colorlr-beetle-wswan
ZmachinefrotzIncludes Zork 1, 2, and 3
  • Navigate to RetroPie menu and press Enter on the keyboard
    • Select RETROPIE SETUP and press Enter on the keyboard
    • Select Manage packages and press enter on the keyboard
    • Select Manage Optional packages and press enter on the keyboard
    • Select %EMULATOR_NAME% and press enter on the keyboard
    • Select Install from source (latest) or Install from binary and press Enter on the keyboard and wait for the installer to finish
      • Select Back and press Enter on the keyboard
      • Optionally, repeat the process to install more emulators or to Exit
        • Select Back and press Enter on the keyboard
        • Select Back and press Enter on the keyboard
        • Select Exit and press Enter on the keyboard

Install Experimental Emulators

SystemExp EmulatorNote
Nintendo DSdrastic
Atari Jaguarlr-virtualjaguarDoes not run great on PI4 still, reports that lr-mess works well
  • Navigate to RetroPie menu and press Enter on the keyboard
  • Select RETROPIE SETUP and press Enter on the keyboard
  • Select Manage packages and press enter on the keyboard
  • Select Manage Experimental packages and press enter on the keyboard
  • Select %EMULATOR_NAME% and press enter on the keyboard
  • Select Install from source (latest) or Install from binary and press Enter on the keyboard and wait for the installer to finish
    • Select Back and press Enter on the keyboard
    • Optionally, repeat the process to install more emulators or to Exit
      • Select Back and press Enter on the keyboard
      • Select Back and press Enter on the keyboard
      • Select Exit and press Enter on the keyboard

Add BIOS and Config Files to the SD Card

  • From a Windows computer containing the BIOS and Config Files, open the samba share for the RetroPie by entering \\retropie
    • If this does not work, navigate to the RetroPie menu, press Enter on the keyboard, select Show IP, press OK on the keyboard and note the IP then press Enter on the keyboard
    • Try to access the samba share for the RetroPie by using the IP address noted above: \\IPADDRESS
  • Copy the BIOS files to the BIOS Share
  • OPTIONAL – Copy Configs to the CONFIGS share
    • You only need to do this if you have previous configurations backed up & do not want to treat this as a brand new install

Prepare the SSD and Copy ROMS

Note: See my article on cleaning ROM collections:
http://sigkillit.com/2019/06/19/clean-no-intro-2018-rom-set/

  • Plug the SSD into a Linux or Windows PC and format the SSD drive as exFAT or FAT32
  • Create a folder called retropie-mount on the root of the drive
  • Mount the SSD drive into the RetroFlag “Cartridge” case
  • Install the cartridge into the RetroFlag case and ensure the SSD firmly inserts into the connector and then power on the RetroPie
  • It will proceed to automatically copy the Retropie folder and all of its content (You may need to reboot to start the copy)
  • From a Windows computer containing the ROM Files, open the samba share for the RetroPie by entering \\retropie
    • If this does not work, navigate to the RetroPie menu, press Enter on the keyboard, select Show IP, press OK on the keyboard and note the IP then press Enter on the keyboard
    • Try to access the samba share for the RetroPie by using the IP address noted above: \\IPADDRESS
  • Copy your ROMs to the ROMS share

Special Configurations for ROMs to Work Properly

Atari 800 / Atari 5200 Load BIOS

  • Launch an Atari 800/5200 game
  • If you launched a 5200 game, select Standard 5200 cartridge option or if you launched a 800 game you won’t see this prompt just continue.  You’ll get a warning that you need a real Atari/OS.
  • Press F1 on the keyboard to open the menu
  • Select Emulator Configuration and press enter on the keyboard
  • Select System ROM Settings and press enter on the keyboard
  • Select Find ROM images in a directory, navigate to the BIOS directory (/home/pi/RetroPie/BIOS) and press the space bar on the keyboard to select that directory.
  • Press Escape on the keyboard to go back a menu
  • Select Save Configuration File and press enter on the keyboard
  • Select Save Configuration on Exit and press enter on the keyboard
  • Press Escape on the keyboard to go back a menu
  • Select Exit Emulator and press enter on the keyboard
  • If you have the correctly named BIOS files and it should automatically configure them

Atari 5200 Auto Load Game (Prevents Memo Pad from Loading)

If the screen loads to a blue memo pad instead of loading the game, this is because it’s set to an 800 system.  You can press F1 and load the rom file each time or you can do the following to override it

  • Launch the Atari 5200 game
  • Using a controller (not keyboard), Press the Select + X (Hotkey + X) to bring up the Quick Menu
    • Select Options and press enter on the keyboard
    • Select Atari System and press the right arrow to select 5200
    • Press B to return to the previous menu
    • Select Resume and press A to return to the game
    • Test to see if it works now.  If it works, you can permanently save it by going back to Controls and selecting Save Core Overrides

Atari 800 / Atari 5200 Configure Controller

UPDATE: Detects “RetroPad” controllers as of version lr-atari800 version 1.9.7

  • Launch Atari 800/5200 game
  • Press the Select + X (Hotkey + X) to bring up the Quick Menu
    • Select Controls and press A
    • Next to user 1 Device Type, press the right arrow to select Atari Joystick
    • Press B to return to the previous menu
    • Select Resume and press A to return to the game
    • Test to see if it works now.  If it works, you can permanently save it by going back to Controls and selecting Save Core Overrides

Sinclair ZX Spectrum

Not all games will work with a joystick!  However, if the input does not seem to be working

  • Launch a ZX Spectrum game
  • Press the Select + X (Hotkey + X) to bring up the Quick Menu
    • Select Controls and press A
    • Next to user 1 Device Type, press the right arrow to select Kempton Joystick or Cursor Joystick
    • Press B to return to the previous menu
    • Select Resume and press A to return to the game
    • Test to see if it works now.  If it works, you can permanently save it by going back to Controls and selecting Save Core Overrides

Install Skyscraper

NOTE: See my article on cleaning ROM collections:
http://sigkillit.com/2019/06/19/clean-no-intro-2018-rom-set/

  • Press F4 on the keyboard to exit emulationstation and enter the terminal
  • Enter the following command at the terminal:
    • sudo ~/RetroPie-Setup/retropie_setup.sh
  • Select Manage Packages and press Enter on the keyboard
  • Select Optional Packages and press Enter on the keyboard
  • Select Skyscraper and press Enter on the keyboard
    • Select Install from pre-built biner or Install from source and press Enter on the keyboard
  • Select Back and press Enter on the keyboard (Repeat 3x’s)
  • Select Exit and press Enter on the keyboard
  • Optional – Add Login for screenscraper.fr, enter the following command at the terminal:
    • sudo nano /opt/retropie/config/all/skyscraper/config.ini
    • Add the below lines
      • [screenscraper]
      • userCreds=”USER:PASSWORD”
      • Replace USER with your username and PASSWORD with your password
    • Press Ctrl + O then press Enter on the keyboard to save the file
    • Press Ctrl + X to exit
    • Type emulationstation and press Enter to go back to emulationstation

Scrape ROMs with Skyscraper

Scrape All Game Data

  • Press F4 on the keyboard to exit emulationstation and enter the terminal
    • Note: once you exit emulationstation, you can SSH into the PI so you can use your TV while you wait for the ROM scrapes to occur which take hours to days. The default username is pi and password is raspberry
  • Enter the following command at the terminal:
    • sudo ~/RetroPie-Setup/retropie_setup.sh
  • Select Manage Packages and press Enter on the keyboard
  • Select Optional Packages and press Enter on the keyboard
  • Select Skyscraper and press Enter on the keyboard
  • Select Configuration/Options and press Enter on the keyboard
  • Select Gather Source and press Enter on the keyboard
    • Choose the source, I recommend using ScreenScraper and TheGamesDB. SkyScraper will use both sources to give you the most complete data
  • Select Gather Resources and press Enter on the keyboard
    • Select the game system you want to scrape (suggest 1 at a time as it takes a few hours & the sources will limit the # of scrapes in a time limit) and press Enter on the keyboard
    • Note: This caches the game info & images in \\retropie\configs\all\skyscraper\ and only needs run once unless you add new games.  It then uses this cache to build the game info, which greatly increases the speed since it all local.  However, you will need to monitor the size of the cache to make sure it does not fill the SD card.
  • Select Generate Games Lists and press Enter on the keyboard
    • Select the game system(s) you want to scrape and press Enter on the keyboard
    • Note: This builds the game images, description, etc
    • Select Run on all systems and press Enter on the keyboard then WAIT!

Fix Incorrect or Missing Scrape Data From ScreenScraper

I found what I consider to be errors or missing game data for some Games on the ScreenScraper DB or GameFAQs (Use it as a parent DB to add new Games) and submitted proposals to correct them or add data. However, some of my requests were denied. I’ve created custom fixes you can download here and the rest of the info will be pulled from TheGamesDB or Screenscraper as an additional source.

  • Download the zip file here
  • Unzip to file to \\retropie\configs\all\skyscraper\import\
  • You will need to Gather Resources and select Import as the Source

Here’s a complete list of the corrections I made:

  • coleco
    • Super Action Soccer (USA, Europe) – Named the game “Super Action Football (soccer)” which causes potential duplicates with “Super Action Football” since the root names match and the brackets should be reserved for info such as region. My suggestion was denied to rename the game to something without brackets such as “Super Action Football aka Soccer” or “Super Action Football ‘Soccer'”. My fix overrides the Title and forces it to use TheGamesDB as the data source for the rest of the game instead of Screenscraper.
  • gb
    • 4 in 1 (Europe) (4B-001, Sachen-Commin) (Unl)
    • 4 in 1 (Europe) (4B-002, Sachen) (Unl)
    • 4 in 1 (Europe) (4B-004, Sachen-Commin) (Unl)
    • 4 in 1 (Europe) (4B-005, Sachen-Commin) (Unl)
    • 4 in 1 (Europe) (4B-006, Sachen) (Unl)
    • 4 in 1 (Europe) (4B-007, Sachen) (Unl)
    • 4 in 1 (Europe) (4B-008, Sachen) (Unl)
    • 4 in 1 (Europe) (4B-009, Sachen) (Unl)
    • 4 in 1 (Taiwan) (En,Zh) (4B-003, Sachen-Commin) (Unl)
      • All of the above games scrape as “4-in-1 Fun Pak” instead of the proper “4 in 1” Sachen games. I tried submitting them as new games but was denied because Wikipedia was not a good enough source (https://en.wikipedia.org/wiki/Thin_Chen_Enterprise). My fix includes generic images and game info.
  • gba
    • 2 Games in 1 – Scooby-Doo + Scooby-Doo 2 – Monsters Unleashed (USA)
    • 2 in 1 – Asterix & Obelix – Bash Them All! + Asterix & Obelix XXL (Europe) (En,Fr,De,Es,It,Nl)
    • 2-in-1 Fun Pack – Shrek 2 + Madagascar (USA)
    • Donkey Kong (USA) (Advance Play Edition)
    • Double Game! – Golden Nugget Casino & Texas Hold ’em Poker (Europe)
    • Golf (USA) (Advance Play Edition)
    • Harry Potter Collection (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da)
    • Hugo 2 in 1 (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi,Pl)
    • Kaisertal – Fight the Necronis War (Europe) (Demo)
    • Pinball (USA) (Advance Play Edition)
    • Soccer (USA) (Advance Play Edition)
    • Tennis (USA) (Advance Play Edition)
    • Winnie the Pooh’s Rumbly Tumbly Adventure & Rayman 3 (Europe) (En,Fr,De,Es,It,Nl+En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)
    • Yoshi’s Cookie (USA) (Advance Play Edition)
      • My requests to add descriptions on the above were denied because any source with info was not good enough. My fix includes a description for these games.
  • gbc
    • Jurassic Boy II + Thunder Blast Man (World) (1B-002, 1B-003, Sachen) (Unl)
    • Space Invasion & Karate Joe (Europe) (Unl)
    • Space Invasion & Painter (Europe) (Unl)
    • Space Invasion (Europe) (Unl)
      • My requests to add descriptions on the above were denied because any source with info was not good enough. My fix includes a description for these games.
  • snes
    • PowerFest 94 – Ken Griffey Jr. Presents Major League Baseball (USA)
    • PowerFest 94 – Super Mario Bros. – The Lost Levels (USA)
    • PowerFest 94 – Super Mario Kart (USA)
      • There is no separate game for the PowerFest 94 versions and it scrapes under the original game. My recommendation was denied because they consider it the same game with a timer. My fix overrides the Title and forces it to use TheGamesDB as the data source for the rest of the game instead of Screenscraper.
May 26

Remove Spam/Phishing Email From All Mailboxes

In 365, you can use Compliance Searches to search and/or remove emails across all of your user’s mailboxes. Compliance searches have replaced the Search-Mailbox cmdlet, which has been deprecated as of April 2020.

Pre-Requisites

You must be a member of the Discovery Management role group or be assigned the Compliance Search management role. Here’s how you can add a user to the Discovery Management group and confirm membership

#Connect to Security and Compliance Center
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session -DisableNameChecking

#Add User to Discovery Management Group (Replace John Doe with your user)
Add-RoleGroupMember -Identity "Discovery Management" -Member "John Doe"

#Confirm Membership
Get-RoleGroupMember -Identity "Discovery Management"

#Disconnect Session
Remove-PSSession $Session

Function to Search & Remove Spam/Phishing Emails From All Mailboxes

Modify the Search Variables below to suite your needs

Function RemoveMaliciousEmails()
{
	#MODIFY THE BELOW VARIABLES - YOU MUST USE THEM ALL AND OPTIONALLY LEAVE SUBJECT BLANK!!!
	$compSearchName = "MaliciousEmail_$(Get-Date -Format "MMddyyyy_HHmm")"
	$compStartDate = "2020-02-24"  #YYYY-MM-DD
	$compEndDate = "2020-02-25"	   #Must be different than Start Date, if searching "today" make this date tomorrow
	$compFrom = "hacker@phishing.com" #You can use just the domain for a wildcard sender BUT USE WITH CAUTION
	$compSubject = "Phishing Test"  #Use backtick ` to escape special characters in the subject such as a quote ex: Dave shared `"New File`" with you
	#
	#DO NOT MODIFY ANYTHING BELOW HERE
	$UserCredential = Get-Credential
	$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
	Import-PSSession $Session -DisableNameChecking
	Write-Host "NAME: $($compSearchName)"
	Write-Host "START: $($compStartDate)"
	Write-Host "END: $($compEndDate)"
	Write-Host "FROM: $($compFrom)"
	Write-Host "SUBJECT: $($compSubject)"
	If ($compSubject.Trim() -eq "")
	{
		$ncs = New-ComplianceSearch -Name $compSearchName -ExchangeLocation all -ContentMatchQuery "(c:c)(received=$compStartDate..$compEndDate)(from=$compFrom)"
	}
	Else
	{
		$ncs = New-ComplianceSearch -Name $compSearchName -ExchangeLocation all -ContentMatchQuery "(c:c)(received=$compStartDate..$compEndDate)(from=$compFrom)(subject=`"$compSubject`")"
	}	
	Write-Host "[$(Get-Date -Format G)] Start Compliance Search: $($compSearchName)"
	Write-Host -NoNewLine "[$(Get-Date -Format G)] Searching"
	Start-ComplianceSearch -Identity $compSearchName
	$resultSearch = Get-ComplianceSearch -Identity $compSearchName
	While ($resultSearch.Status -ne "Completed")
	{
		$resultSearch = Get-ComplianceSearch -Identity $compSearchName
		Write-Host -NoNewLine "."
		Start-Sleep -s 5
	}
	If (($resultSearch.Items -le 0) -OR ([string]::IsNullOrWhiteSpace($resultSearch.SuccessResults)))
	{
		Write-Host "`n[$(Get-Date -Format G)] Search completed with 0 successful results, Invalid Search Quitting!" -ForegroundColor Red
		Remove-PSSession $Session
		Return
	}
	Elseif ($resultSearch.Items -ge 500)
	{
		Write-Host "`n[$(Get-Date -Format G)] Search Completed with $($resultSearch.Items) successful results, you will need to run mulitple times!" -ForegroundColor Yellow
	}
	Else 
	{
		Write-Host "`n[$(Get-Date -Format G)] Search Completed with $($resultSearch.Items) successful results"
	}
	Write-Host "[$(Get-Date -Format G)] Create Preview and Export of Results"
	Write-Host -NoNewLine "[$(Get-Date -Format G)] Processing"
	$ncsa = New-ComplianceSearchAction -SearchName $compSearchName -Preview
	$resultPreview = Get-ComplianceSearchAction -Identity "$($compSearchName)_Preview"
	$ncsa = New-ComplianceSearchAction -SearchName $compSearchName -Export -ExchangeArchiveFormat SinglePst -Format FxStream
	$resultExport = Get-ComplianceSearchAction -Identity "$($compSearchName)_Export"
	While ($resultPreview.Status -ne "Completed" -AND $resultExport.Status -ne "Completed")
	{
		$resultPreview = Get-ComplianceSearchAction -Identity "$($compSearchName)_Preview"
		$resultExport = Get-ComplianceSearchAction -Identity "$($compSearchName)_Export"
		#Write-Host "[$(Get-Date -Format G)] Processing..."
		Write-Host -NoNewLine "."
		Start-Sleep -s 5
	}
	Write-Host "`n[$(Get-Date -Format G)] Preview and Export successfully created"
	Write-Host "[$(Get-Date -Format G)] View Results at https://protection.office.com/" 
	Write-Host "[$(Get-Date -Format G)] Preview: Search -> Content Search -> Searches tab -> $($compSearchName)"
	Write-Host "[$(Get-Date -Format G)] Export:  Search -> Content Search -> Exports tab -> $($compSearchName)_Export"
	Write-Host "[$(Get-Date -Format G)] Start Purging emails"
	Write-Host -NoNewLine "[$(Get-Date -Format G)] Purging"
	$ncsa = New-ComplianceSearchAction -SearchName $compSearchName -Purge -PurgeType HardDelete -Confirm:$False
	$resultPurge = Get-ComplianceSearchAction -Identity "$($compSearchName)_Purge"
	While ($resultPurge.Status -ne "Completed")
	{
		$resultPurge = Get-ComplianceSearchAction -Identity "$($compSearchName)_Purge"
		Write-Host -NoNewLine "."
		Start-Sleep -s 5
	}
	Write-Host "`n[$(Get-Date -Format G)] Purge complete"
	$confirmDelete = Read-Host "Delete Compliance Search, Preview, Export, and Purge? [Y/N]"
	If ($confirmDelete -eq 'Y') 
	{
		Write-Host "[$(Get-Date -Format G)] Deleting Compliance Search, Preview, Export, and Purge..."
		Remove-ComplianceSearchAction -Identity "$($compSearchName)_Preview" -Confirm:$False
		Remove-ComplianceSearchAction -Identity "$($compSearchName)_Export" -Confirm:$False
		Remove-ComplianceSearchAction -Identity "$($compSearchName)_Purge" -Confirm:$False
		Remove-ComplianceSearch -Identity $compSearchName -Confirm:$False
	}
	Write-Host "[$(Get-Date -Format G)] Removing Powershell Session..."
	Remove-PSSession $Session
}
RemoveMaliciousEmails
Apr 19

Output Multiple Colors on a Single Line

#Requires -Version 4.0
Function Write-Out
{
	<#
	.SYNOPSIS
	Write output to console and/or log file. Supports multiple foreground and background colors on a single line and 
	advanced options to insert lines, spaces, or tabs before or after the text as well as timestamps.
	.DESCRIPTION
	Uses Write-Host to output color text to the console, and supports using multiple foreground and background colors 
	on a single line. Uses Set-Content and Add-Content to output to a file.
	
	It works by accepting strings as an array and then you can assign the foreground or background colors by passing
	them as arrays for the matching text. It also accepts Default which allows you skip specifying a color and use the 
	default.
	
	This function requires Powershell 4.0 to support the alias attribute in the function.
	.PARAMETER Text
	Optional. Array of strings to write to console/log. If you do not need to use multiple colors on a single line you can pass a single string instead of an array of strings. Otherwise, if you need to multiple colors on a single line pass an array of strings and associated colors using -ForegroundColor, -BackgroundColor, -ForegroundColorDefault, or -BackgroundColorDefault.
	.PARAMETER ForegroundColor
	Optional. Array of foreground Colors to write to console. Default means the ForegroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER ForegroundColorDefault
	Optional. Default foreground color to write to console. Default is set to white.
	Valid Colors: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColor
	Optional. Array of background colors to write to console. Default means the BackgroundColorDefault value is used.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER BackgroundColorDefault
	Optional. Default background color to write to console. Default is none.
	Valid Colors: Default, Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
	.PARAMETER PreLine
	Optional. Add blank lines before your text. Default is 0.
	.PARAMETER PreSpace
	Optional. Add spaces before your text. Default is 0.
	.PARAMETER PreTab
	Optional. Add Tabs before your text. Default is 0.
	.PARAMETER TimeStampFormat
	Optional. Format used for time stamps. Default is 'yyyy-MM-dd HH:mm:ss'
	.PARAMETER TimeStamp
	Optional. Adds time stamp in square brackets before your text.
	.PARAMETER ClearHost
	Optional. Clear the console before your text.
	.PARAMETER PostLine
	Optional. Add blank lines after your text. Default is 0.
	.PARAMETER PostSpace
	Optional. Add spaces after your text. Default is 0.
	.PARAMETER PostTab
	Optional. Add tabs after your text. Default is 0.
	.PARAMETER NoNewLine
	Optional. Do not add a new line after your text and optional post text options. Default is to add a new line.
	.PARAMETER LogFile
	Optional. Absolute or relative path of the log file.
	.PARAMETER Overwrite
	Optional. Ovewrite the log file. Default is to append to the log file.
	.INPUTS
	Parameters above
	.OUTPUTS
	None
	.NOTES
	Author: Brian Steinmeyer
	URL: http://sigkillit.com/
	Created: 4/18/2020
	Version 1.3
	- Added LogOnly Option for only writing text to a log file
	Version 1.2
	- Set Text, ForegroundColor, and BackgroundColor to default value of @() to fix errors checking counts in some circumstances
	- Fixed an issue where ForegroundColorDefault and BackgroundColorDefault were not working properly in some circumstances
	- Added Requires -Version 4.0
	Version 1.1
	- Completely rewrote the "Main Text" section
	- Added "Default" as a color option, which allows you to use the default values for foreground/background 
	  - Useful when you want to specify a backgroundcolor in certain parts of a line like the middle
	  - Ex: Write-Out -Text "How to ","highlight ","the middle text" -BackgroundColor Default,Yellow,Default
	Version: 1.0
	- Initial Creation inspired by PSWriteColor (https://github.com/EvotecIT/PSWriteColor)
	  - Improved upon by switching Foreground and Background Colors to default values if colors are not specifid for all strings.
	    Will also ignore extra colors if more colors are specified than strings specified.
	.EXAMPLE
	Write-Out -Text "Start with Red Text ","Then Switch to Blue Text ","Now Magenta" -ForegroundColor Red,Blue,Magenta 
	.EXAMPLE
	Write-Out -Text "White on Black ","Black on White ","Dark Cyan on Cyan ","Yellow on Green ","Default Color" -ForegroundColor White,Black,DarkCyan,Yellow -BackgroundColor Black,White,Cyan,Green
	.EXAMPLE
	Write-Out -Text "Make this"," entire line"," the same color by setting defaults" -ForegroundColorDefault Yellow -BackgroundColorDefault Magenta
	.EXAMPLE
	Write-Out -Text "Add a blank line and two tabs ","before ","my text" -ForegroundColor Green,Cyan,White -PreLine 1 -PreTab 2
	.EXAMPLE
	Write-Out -Text "Add two blank ","lines ","after my text" -ForegroundColor White,Green,White -PostLine 2
	.EXAMPLE
	Write-Out -Text "Add 3 spaces before my text" -ForegroundColor Gray -Prespace 3
	.EXAMPLE
	Write-Out -Text "White text and a tab after" -ForegroundColor White -NoNewLine -PostTab 1
	Write-Out -Text "Black text on Yellow ","and then back to white" -ForegroundColor Black,White -BackgroundColor Yellow
	.EXAMPLE
	Write-Out -Text "An easy way to ","highlight ","text in the middle" -BackgroundColor Default,Yellow
	.EXAMPLE
	Write-Out -Text "You can even add a ","time stamp ","before your output" -ForegroundColor White,Green,White -TimeStamp -PreLine 3
	.EXAMPLE
	Write-Out -Text "You can change the ","time stamp format" -ForegroundColor White,Yellow -TimeStamp -TimeStampFormat "dd-MM-yyy HH:mm" -PreLine 1 -PostLine 1
	.EXAMPLE
	Write-Out -Text "An"," Error"," occurred let's write overwrite/create a new log file" -ForegroundColor White,Red,White -TimeStamp -LogFile "script.log" -Overwrite
	.EXAMPLE
	Write-Out -Text "Now you can ","Append ","this line to your log file" -ForegroundColor Cyan,Magenta -TimeStamp -LogFile "script.log"
	#>
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory=$false)]
		[AllowEmptyString()]
        [alias ('T')]
		[String[]]
		$Text = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGC', 'FC')]
		[string[]]
		$ForegroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('FGCD', 'FCD')]
		[string]
		$ForegroundColorDefault = [ConsoleColor]::White,
		[Parameter(Mandatory=$false)]
        [ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGC', 'BC')]
		[String[]]
		$BackgroundColor = @(),
		[Parameter(Mandatory=$false)]
		[ValidateSet("Default","Black","DarkBlue","DarkGreen","DarkCyan","DarkRed","DarkMagenta","DarkYellow","Gray","DarkGray","Blue","Green","Cyan","Red","Magenta","Yellow","White")]
		[alias ('BGCD', 'BCD')]
		[string]
		$BackgroundColorDefault = "Default",
		[Parameter(Mandatory=$false)]
		[int]
		$PreLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PreTab = 0,
		[Parameter(Mandatory=$false)]
		[Alias('TSF', 'TS')]
		[string]
		$TimeStampFormat = 'yyyy-MM-dd HH:mm:ss',
		[Parameter(Mandatory=$false)]
		[switch]
		$TimeStamp,		
		[Parameter(Mandatory=$false)]
		[switch]
		$ClearHost,
		[Parameter(Mandatory=$false)]
		[int]
		$PostLine = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostSpace = 0,
		[Parameter(Mandatory=$false)]
		[int]
		$PostTab = 0,
		[Parameter(Mandatory=$false)]
		[switch]
		$NoNewLine = $false,
		[Parameter(Mandatory=$false)]
		[alias ('Log', 'L')]
		[string]
		$LogFile = '',
		[Parameter(Mandatory=$false)]
		[switch]
		$LogOnly = $false,
		[Parameter(Mandatory=$false)]
		[switch]
		$Overwrite = $false
	)
	
	Begin
	{
		#Nothing to Do Here
	}
	
	Process
	{
		#Optional - Do Not Write to ConsoleColor
		If (!($LogOnly))
		{
			#Optional - Prefix Text 
			If ($ClearHost) { Clear-Host }
			If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) before Main text
			If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) before Main text
			If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) before Main text
			If ($TimeStamp) { Write-Host -Object "[$([datetime]::Now.ToString($TimeStampFormat))]" -NoNewline } # Add Timestamp before Main Text

			#MAIN TEXT
			If (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0))
			{
				#Text Only Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				} 
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -eq 0))
			{ 
				#Text and ForegroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColorDefault -eq "Default")
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -gt 0))
			{
				#Text and BackgroundColor Specified
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColorDefault -NoNewLine
						}
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColorDefault -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			ElseIf (($Text.Count -gt 0) -AND ($ForegroundColor.Count -gt 0) -AND ($BackgroundColor.Count -gt 0))
			{ 
				#Text, ForegroundColor, and BackgroundColor Specified (FAILS NOT WRITING DEFAULT)
				For ($i = 0; $i -lt $Text.Count; $i++)
				{
					If ($ForegroundColor.Count -le $i) { $ForegroundColor += $ForegroundColorDefault } #ForegroundColor Not Specified Set to Default
					If ($BackgroundColor.Count -le $i) { $BackgroundColor += $BackgroundColorDefault } #BackgroundColor Not Specified Set to Default
					If ($ForegroundColor[$i] -eq "Default") { $ForegroundColor[$i] = $ForegroundColorDefault }
					If ($BackgroundColor[$i] -eq "Default")
					{
						If ($BackgroundColorDefault -eq "Default")
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -NoNewLine
						}
						Else
						{
							Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColorDefault -NoNewLine
						}					
					}
					Else 
					{
						Write-Host $Text[$i] -ForegroundColor $ForegroundColor[$i] -BackgroundColor $BackgroundColor[$i] -NoNewLine
					}
				}
			}
			Else
			{
				#No Text, ForegroundColor, or BackgroundColor Specified ($Text.Count -eq 0) -AND ($ForegroundColor.Count -eq 0) -AND ($BackgroundColor.Count -eq 0)
			}
				
			#Post Text
			If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { Write-Host -Object " " -NoNewLine } }  # Add Tab(s) after Main text
			If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { Write-Host -Object "`t" -NoNewLine } }  # Add Space(s) after Main text
			If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line(s) after Main text
			If ($NoNewLine) { Write-Host -NoNewline } else { Write-Host } # Add New Line after Main Text Unless NoNewLine is Supplied
		}
		
		#Log File
		$TextToWrite = "" #Build Text to Write to Log File
		If ($PreLine -gt 0) { For ($i = 0; $i -lt $PreLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) before Main text
		If ($PreSpace -gt 0) { For ($i = 0; $i -lt $PreSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) before Main text
		If ($PreTab -gt 0) { For ($i = 0; $i -lt $PreTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) before Main text
		If ($TimeStamp) { $TextToWrite += "[$([datetime]::Now.ToString($TimeStampFormat))]" } # Add Timestamp before Main Text
		If ($Text.Count -gt 0) { For ($i = 0; $i -lt $Text.Count; $i++) { $TextToWrite += $Text[$i] } } #Add Main Text
		If ($PostSpace -gt 0) { for ($i = 0; $i -lt $PostSpace; $i++) { $TextToWrite += " " } }  # Add Tab(s) after Main text
		If ($PostTab -gt 0) { for ($i = 0; $i -lt $PostTab; $i++) { $TextToWrite += "`t" } }  # Add Space(s) after Main text
		If ($PostLine -gt 0) { for ($i = 0; $i -lt $PostLine; $i++) { $TextToWrite += "`n" } } # Add empty line(s) after Main text		
		If ($LogFile -eq "")
		{
			#No LogFile Specified - Skip
		}
		ElseIf(!(Test-Path -Path $LogFile))
		{
			#Create Log File If Does Not Exist
			If ($NoNewLine)
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite
			}
			Else 
			{
				$nf = New-Item -Path $LogFile -type file -force -Value $TextToWrite`r`n
			}
		}
		Else
		{
			#Log File Exists			
			If($Overwrite)
			{
				#Overwrite Log File
				If ($NoNewLine)
				{
					Set-Content -Path $LogFile -Value $TextToWrite -NoNewline
				}
				Else 
				{
					Set-Content -Path $LogFile -Value $TextToWrite
				}
			}
			Else
			{
				#Append Log File
				Try {
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite -ErrorAction Stop
					}
				} Catch {
					Start-Sleep -s 3
					If ($NoNewLine)
					{
						Add-Content -Path $LogFile -Value $TextToWrite -NoNewline
					}
					Else 
					{
						Add-Content -Path $LogFile -Value $TextToWrite
					}
				}
			}
		}
	}
	
	End
	{
		#Nothing to Do Here
	}
}