Find Duplicate Files with PowerShell

This one-liner will find duplicate files in the current directory and all sub-directories. It uses hash values of the files, so it doesn’t matter if the file names have changed. If the content is the same, the hash will be the same and it will be considered a duplicate.

# find duplicate files
# Kenward Bradley 2016-12-29
Get-ChildItem -Recurse | Get-FileHash | Group-Object -Property Hash | Where-Object Count -GT 1 | foreach {$_.Group | select Path, Hash}

PowerShell: Return Debug and Verbose messages From Your Functions

Debug and Verbose let you return extra information from your scripts, for specific situations, and only when needed. Debug lets you return “programmer-level” information. Verbose lets you return more detailed information to the user. I’ll provide some examples.

Continue reading “PowerShell: Return Debug and Verbose messages From Your Functions”

Compare services of two computers in PowerShell

This function “Compare-Services” will allow you to compare the running services of two computers. The first one is called the Reference Computer, the second is the Difference Computer. The output defaults to Compare-Object output. If you need an object to do further work with, this is what you want. If you don’t want to puzzle out what the “=>” and “<=” indicators mean, and you want a simple report, you can use the -FriendlyText switch will give you English text output, which will make more sense to many humans, at least the English speaking humans reading this blog. Windows remoting needs to be enabled on the computers being checked for this to work.

Example output:

Compare-Object style (PS object) output:

InputObject                           SideIndicator
-----------                           -------------
Distributed Link Tracking Client      =>           
Active Directory Web Services         <=           
Application Information               <=           
DFS Namespace                         <=           
DFS Replication                       <=           
DHCP Server                           <=           
DNS Server                            <=           
Intersite Messaging                   <=           
Kerberos Key Distribution Center      <=           
Active Directory Domain Services      <=           
Smart Card Device Enumeration Service <=           
Shell Hardware Detection              <=           
Virtual Disk                          <=           

Friendly Text (English) output:
"Distributed Link Tracking Client" is on lab01 (not on dc1)
"Active Directory Web Services" is on dc1 (not on lab01)
"Application Information" is on dc1 (not on lab01)
"DFS Namespace" is on dc1 (not on lab01)
"DFS Replication" is on dc1 (not on lab01)
"DHCP Server" is on dc1 (not on lab01)
"DNS Server" is on dc1 (not on lab01)
"Intersite Messaging" is on dc1 (not on lab01)
"Kerberos Key Distribution Center" is on dc1 (not on lab01)
"Active Directory Domain Services" is on dc1 (not on lab01)
"Smart Card Device Enumeration Service" is on dc1 (not on lab01)
"Shell Hardware Detection" is on dc1 (not on lab01)
"Virtual Disk" is on dc1 (not on lab01)

And here is the function:

function Compare-Services ($ReferenceComputer, $DifferenceComputer, [switch]$FriendlyText) {

$ReferenceServices = (Get-Service -ComputerName $ReferenceComputer | where Status -EQ "Running").DisplayName
$DifferenceServices = (Get-Service -ComputerName $DifferenceComputer | where Status -EQ "Running").DisplayName

$CompareResult = Compare-Object -ReferenceObject $ReferenceServices -DifferenceObject $DifferenceServices

if ($FriendlyText) {
    $CompareResult | foreach {
        if ($_.SideIndicator -eq "=>") {"`"$($_.InputObject)`" is on $DifferenceComputer (not on $ReferenceComputer)"}
        if ($_.SideIndicator -eq "<=") {"`"$($_.InputObject)`" is on $ReferenceComputer (not on $DifferenceComputer)"}
    else {$CompareResult}


PowerShell cmdlet to create lab users

So you have Active Directory in your lab and want to create a bunch of users for some reason? My PowerShell cmdlet “New-LabADUser” should be just what you need.

Usage examples:

PS C:\> New-LabADUser

DistinguishedName : CN=BStevens,CN=Users,DC=south,DC=lab
Enabled           : True
GivenName         : Bonnie
Name              : BStevens
ObjectClass       : user
ObjectGUID        : 33319b1b-bf93-4647-8e8d-d193be91043d
SamAccountName    : BStevens
SID               : S-1-5-21-4025744032-3205137867-863233111-1711
Surname           : Stevens
UserPrincipalName : 

PS C:\> New-LabADUser -NumberOfUsers 3 | select Name


PS C:\> $UsersCreated = New-LabADUser -NumberOfUsers 10 | select DistinguishedName

PS C:\> $UsersCreated


Users are created with Organization = “LabUser” to make them easy to find:

PS C:\>  Get-ADUser -Filter "Organization -EQ 'LabUser'" | select Name


And the cmdlet…

function New-LabADUser
 # Param2 help description
 [int]$NumberOfUsers = 1

# build the names lists
 $GivenName = ("Aaron", "Adam", "Alan", "Albert", "Alice", "Amanda", "Amy", "Andrea", "Andrew", "Angela", "Ann", "Anna", "Anne", "Annie", "Anthony", "Antonio", "Arthur", "Ashley", "Barbara", "Benjamin", "Betty", "Beverly", "Billy", "Bobby", "Bonnie", "Brandon", "Brenda", "Brian", "Bruce", "Carl", "Carlos", "Carol", "Carolyn", "Catherine", "Charles", "Cheryl", "Chris", "Christina", "Christine", "Christopher", "Clarence", "Craig", "Cynthia", "Daniel", "David", "Deborah", "Debra", "Denise", "Dennis", "Diana", "Diane", "Donald", "Donna", "Doris", "Dorothy", "Douglas", "Earl", "Edward", "Elizabeth", "Emily", "Eric", "Ernest", "Eugene", "Evelyn", "Frances", "Frank", "Fred", "Gary", "George", "Gerald", "Gloria", "Gregory", "Harold", "Harry", "Heather", "Helen", "Henry", "Howard", "Irene", "Jack", "Jacqueline", "James", "Jane", "Janet", "Janice", "Jason", "Jean", "Jeffrey", "Jennifer", "Jeremy", "Jerry", "Jesse", "Jessica", "Jimmy", "Joan", "Joe", "John", "Johnny", "Jonathan", "Jose", "Joseph", "Joshua", "Joyce", "Juan", "Judith", "Judy", "Julia", "Julie", "Justin", "Karen", "Katherine", "Kathleen", "Kathryn", "Kathy", "Keith", "Kelly", "Kenneth", "Kevin", "Kimberly", "Larry", "Laura", "Lawrence", "Lillian", "Linda", "Lisa", "Lois", "Lori", "Louis", "Louise", "Margaret", "Maria", "Marie", "Marilyn", "Mark", "Martha", "Martin", "Mary", "Matthew", "Melissa", "Michael", "Michelle", "Mildred", "Nancy", "Nicholas", "Nicole", "Norma", "Pamela", "Patricia", "Patrick", "Paul", "Paula", "Peter", "Philip", "Phillip", "Phyllis", "Rachel", "Ralph", "Randy", "Raymond", "Rebecca", "Richard", "Robert", "Robin", "Roger", "Ronald", "Rose", "Roy", "Ruby", "Russell", "Ruth", "Ryan", "Samuel", "Sandra", "Sara", "Sarah", "Scott", "Sean", "Sharon", "Shawn", "Shirley", "Stephanie", "Stephen", "Steve", "Steven", "Susan", "Tammy", "Teresa", "Terry", "Theresa", "Thomas", "Timothy", "Tina", "Todd", "Victor", "Virginia", "Walter", "Wanda", "Wayne", "William", "Willie")
 $Surame = ("Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", "Miller", "Wilson", "Moore", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner", "Phillips", "Campbell", "Parker", "Evans", "Edwards", "Collins", "Stewart", "Sanchez", "Morris", "Rogers", "Reed", "Cook", "Morgan", "Bell", "Murphy", "Bailey", "Rivera", "Cooper", "Richardson", "Cox", "Howard", "Ward", "Torres", "Peterson", "Gray", "Ramirez", "James", "Watson", "Brooks", "Kelly", "Sanders", "Price", "Bennett", "Wood", "Barnes", "Ross", "Henderson", "Coleman", "Jenkins", "Perry", "Powell", "Long", "Patterson", "Hughes", "Flores", "Washington", "Butler", "Simmons", "Foster", "Gonzales", "Bryant", "Alexander", "Russell", "Griffin", "Diaz", "Hayes", "Myers", "Ford", "Hamilton", "Graham", "Sullivan", "Wallace", "Woods", "Cole", "West", "Jordan", "Owens", "Reynolds", "Fisher", "Ellis", "Harrison", "Gibson", "Mcdonald", "Cruz", "Marshall", "Ortiz", "Gomez", "Murray", "Freeman", "Wells", "Webb", "Simpson", "Stevens", "Tucker", "Porter", "Hunter", "Hicks", "Crawford", "Henry", "Boyd", "Mason", "Morales", "Kennedy", "Warren", "Dixon", "Ramos", "Reyes", "Burns", "Gordon", "Shaw", "Holmes", "Rice", "Robertson", "Hunt", "Black", "Daniels", "Palmer", "Mills", "Nichols", "Grant", "Knight", "Ferguson", "Rose", "Stone", "Hawkins", "Dunn", "Perkins", "Hudson", "Spencer", "Gardner", "Stephens", "Payne", "Pierce", "Berry", "Matthews", "Arnold", "Wagner", "Willis", "Ray", "Watkins", "Olson", "Carroll", "Duncan", "Snyder", "Hart", "Cunningham", "Bradley", "Lane", "Andrews", "Ruiz", "Harper", "Fox", "Riley", "Armstrong", "Carpenter", "Weaver", "Greene", "Lawrence", "Elliott", "Chavez", "Sims", "Austin", "Peters", "Kelley", "Franklin", "Lawson")
 for ($i = 1; $i -le $NumberOfUsers; $i++)
 $gn = $GivenName | Get-Random
 $sn = $Surame | Get-Random
 $AccountName = $gn.Substring(0,1) + $sn

# create a password to fit most requirements
$pw = ""
 do {$pw += (33..126) | foreach {[char]$_} | Get-Random}
 until ($pw.Length -ge 16)
 $AccountPassword = ConvertTo-SecureString $pw -AsPlainText -Force

New-ADUser -AccountPassword $AccountPassword -GivenName $gn -Surname $sn -Name $AccountName -Organization "LabUser" -Enabled $true -PassThru



The Script Development Process

Script Development Process
Script Development Process

This script development process is an iterative process designed to help you rapidly write quality scripts. The principle is to understand and define the problem. Break up your solution into small steps. Code one small step at a time. Test each small step and get it right before moving on to the next piece. Repeat these steps until you have your solution. All while managing your time.

When is automation the solution?

The benefits of automation are primarily to save time and to decrease errors associated with manual actions. You will need to make the calculation if a script will benefit your situation. How many hours will it take for you to write these script? How many hours of manual work will the script save?

Continue reading “The Script Development Process”

Output the PowerShell assignment command from an array

This function will take an array and output the PowerShell assignment command with the elements from the array. It would be useful if you want to hardcode a string array and don’t want to manually format it.

The function is below, along with an example usage and example output.

function ArrayAssignment ($MyArray) {
    $ArrayContent = "`$Array = @("
    # Output elements except the last
    $MyArray[0..$($MyArray.Count-2)] | foreach {$ArrayContent += "`"$_`","}
    # Output the last element
    $ArrayContent += "`"$($MyArray[$MyArray.Count-1])`")"

# Create an array of the first 20 services names for an example
$Example = (Get-Service).DisplayName[0..19]

# Use the function
ArrayAssignment $Example

# Example Output:
PS C:\>
$Array = @("Adobe Flash Player Update Service","AllJoyn Router Service","Application Layer Gateway Service","AMD External Events Utility","AMD FUEL Service","Application Identity","Application Information","Apple Mobile Device Service","Application Management","App Readiness","AppX Deployment Service (AppXSVC)","ASP.NET State Service","Windows Audio Endpoint Builder","Windows Audio","ActiveX Installer (AxInstSV)","BitLocker Drive Encryption Service","Base Filtering Engine","Background Intelligent Transfer Service","Bonjour Service","Background Tasks Infrastructure Service")

Get the IP addresses of Hyper-V VMs, part 2

A variation on my previous post.

This one-liner uses an expression in a select to expose the IP addresses. The expression is necessary so we can drill down into the NetworkAdapters collection to get to the IPAddresses property.

This will list all IP addresses for each VM:

Get-VM | select Name, State, @{Name="IP";Expression={$_.NetworkAdapters.IPAddresses}}

This next variation displays only the first IP address for each VM. Note the “[0]”. It addresses the first element of “IPAddresses” array.

Get-VM | select Name, State, @{Name="IP";Expression={$_.NetworkAdapters.IPAddresses[0]}}

New-Nano: automating Windows Nano Server lab builds with PowerShell

I wrote this function to automate Nano Server lab builds into Hyper-V. You need to supply the “Windows Server 2016 Technical Preview 5” ISO, which you can get here or here. My script is based partially on ideas presented in the Microsoft TechNet article “Getting Started with Nano Server“.

You will need to have Hyper-V on your computer, have a Hyper-V switch on a network that has DHCP running somewhere (if you have a switch in Hyper-V defined as “External”, this should work fine). You will also need to supply a working directory.

New-Nano copies the required files from the ISO, runs Microsoft’s cmdlet New-NanoServerImage to build a Hyper-V disk image, creates a new VM from that disk image, starts the VM, and gets network information from the VM. The function then runs an example PowerShell Session to demonstrate connectivity to the new Nano VM. Total build time in my tests is a little over three minutes.

If you look to the bottom of the script, you will see an example of how to call the New-Nano function.

Further refinements would be to install features and packages to the Nano, and to do domain joins. Please note that this should only be used for a lab environment because the administrator password is in plaintext. If this was going into a production environment, you would want to encode it.

Enjoy. Please try it out and leave a comment.

Start-Transcript -Path "C:\Lab\NewNano.txt"
function New-Nano
        # Path to the Windows Server 2016 ISO

        # Working Directory

        # Admin Password
        $AdminPassword = (ConvertTo-SecureString -AsPlainText "Password1" -Force),

        # Hyper-V Switch Name
        $NetworkSwitch = "External",

        # Nano VM Name
        $Name = "nano" + $(Get-Random -Minimum 1000 -Maximum 9999),
        # Processor Count
        $ProcessorCount = 1,

        # Memory Startup Bytes
        $MemoryStartupBytes = 512MB

    # ref:
    $VirtualHardDiskPath = $(Get-VMHost).VirtualHardDiskPath
    $VirtualMachinePath = $(Get-VMHost).VirtualMachinePath
    $NewNanoPath = $VirtualHardDiskPath + $Name + ".vhdx"
    $StartingDir = (Get-Location).Path
    $MountObject = Mount-DiskImage -ImagePath $ImagePath -PassThru
    $MountDriveLetter = ($MountObject | Get-Volume).DriveLetter + ":"
    $NanoMount = $MountDriveLetter + "\NanoServer\"

    # copy the NanoServer dir to the working dir
    $NanoDir = $WorkingDir + "NanoServer\"
    Copy-Item -Path $NanoMount -Destination $NanoDir -Recurse -Force

    Set-Location $NanoDir

    Import-Module .\NanoServerImageGenerator -Verbose

    # create the VHD
    $NewNanoResult = New-NanoServerImage -DeploymentType Guest -Edition Standard -MediaPath $MountDriveLetter -BasePath .\Base -TargetPath $NewNanoPath -EnableRemoteManagementPort -ComputerName $Name -AdministratorPassword $AdminPassword

    # new VM from the VHD
    New-VM -Name $Name -VHDPath $NewNanoPath -MemoryStartupBytes $MemoryStartupBytes -SwitchName $NetworkSwitch -BootDevice VHD -Generation 2 -Path $VirtualMachinePath  |
    Set-VM -DynamicMemory -ProcessorCount $ProcessorCount -Passthru -AutomaticStopAction ShutDown -AutomaticStartAction Nothing 

    # finish up.
    $MountObject | Dismount-DiskImage
    "Starting $Name"
    Start-VM $Name

    while ((Get-VM $Name).State -ne "Running")
        {Start-Sleep 5}
    "$Name is Running."

    while ((Get-VM $Name).NetworkAdapters.Status[0] -ne "Ok") 
        {Start-Sleep 5}
    "Network adapter reporting Ok."

    $vm = Get-VM $Name
    $vm.NetworkAdapters | select VMName, SwitchName, Status, IPAddresses

    "Demonstrate that PowerShell Session works"
    $AdminUser = "$Name\Administrator"
    $cred = New-Object -Typename System.Management.Automation.PSCredential  -Argumentlist $AdminUser, $AdminPassword
    $pss = New-PSSession -ComputerName $Name -Credential $cred
    Invoke-Command -Session $pss -ScriptBlock {Get-Service | where Status -EQ "Running"| Format-Table} 
    Remove-PSSession $pss

    # return to original directory
    Set-Location $StartingDir

$NewNanoParams = @{
    ImagePath = "C:\ISOs\en_windows_server_2016_technical_preview_5_x64_dvd_8512312.iso";
    WorkingDir = "C:\Lab\";
    AdminPassword =  (ConvertTo-SecureString -AsPlainText "Password1" -Force);
    NetworkSwitch = "External"
    Name = "LabNano" + (Get-Random -Minimum 100 -Maximum 999);
    ProcessorCount = 1;
    MemoryStartupBytes = 512MB

New-Nano @NewNanoParams




Returning custom objects in PowerShell, simplified

I’ve stumbled upon this simplified technique to return custom objects in my PowerShell functions.

My first take on it was this, but I’ve simplified it. In this simplified variation, you just create and return the object in the same hash. For example:

    ComputerName = $ComputerSystem.DNSHostName;
    Domain = $ComputerSystem.Domain;
    ProcessCount = $ProcessCount;

This is a hash, as you probably have seen before, but the [PsCustomObject] is what makes it a custom object.

And in context of a function:

# Example Function
function Get-SystemInfo {

    # Prepare the info
    $OS = Get-CimInstance -ClassName Win32_OperatingSystem # for FreePhysicalMemory, Caption, PSComputerName, SystemDrive
    $LoadPercentage = (Get-CimInstance -ClassName Win32_Processor).LoadPercentage
    $ProcessCount = (Get-Process).Count
    $SystemDrive = Get-CimInstance -ClassName Win32_LogicalDisk -filter "Name = '$($OS.SystemDrive)'" 
    $ComputerSystem = Get-CimInstance -ClassName Win32_ComputerSystem

    # Return the properties in a custom object
        ComputerName = $ComputerSystem.DNSHostName;
        Domain = $ComputerSystem.Domain;
        FreePhysicalMemory = "{0:n0}" -f ($OS.FreePhysicalMemory /1KB) + " MB";
        OS = $OS.Caption;
        LastBootUpTime = $OS.LastBootUpTime;
        LoadPercentage = $LoadPercentage;
        ProcessCount = $ProcessCount;
        SysVolFree = "{0:n1}" -f ($SystemDrive.FreeSpace /1GB) + " GB"

# Example Usage

# Using the results, put them into an object "$Result"
$Result = Get-SystemInfo

# Access the values inside the object