PowerShell: Finding folders that don’t match a pattern

This week I came across an issue where some folders didn’t match a naming convention that was required by another third-party system. Because of this, data wasn’t being extracted from all of the incorrectly-named folders.

The naming convention in question goes like this: “{whatever} {4-digit-number}”. Lots of folders were missing the relevant number at the end.

To quickly identify which folders were wrongly-named, I used a regex that searches the end of a string for a space followed by four numbers. I then used that in conjunction with Where-Object:

Get-ChildItem -Path | Where-Object {$_.Name -notmatch " d{4}$"}

Then, so that I could identify who was naming new folders incorrectly, I then did this:

Get-ChildItem -Path  | Where-Object {$_.Name -notmatch " d{4}$"} | Select-Object -Property Name,CreationTime,@{Name="Owner";Expression={(Get-Owner $_.FullName).Owner.AccountName}}

I then piped the above command to Sort-Object, and exported the lot to a CSV file for the department in question to review:

Get-ChildItem -Path  | Where-Object {$_.Name -notmatch " d{4}$"} | Select-Object -Property Name,CreationTime,@{Name="Owner";Expression={(Get-Owner $_.FullName).Owner.AccountName}}| Sort-Object CreationTime -Descending | Export-Csv c:tempinsolfolder.csv -NoTypeInformation

In all, a quick, easy, and repeatable way of getting a report out to the people who need to maintain the folder structure. It’s possible to extend it further to just email the owners of each of the non-compliant folders on a set schedule.

Delete old log files with PowerShell

Today I came across a folder full of log files created by an import/integration application on one of our servers. The folder contained over 50,000 files. Rather than manually delete the old logs, here’s how I removed all items older than 1 month:

Get-ChildItem "C:Path_To_logs*.txt" | Where-Object {$_.CreationTime -lt (Get-Date).AddMonths(-1)} | Remove-Item -Force -Verbose -WhatIf

Since PowerShell is based on the .NET Framework, you can use standard System.DateTime methods when working with dates and times.

PS C:> Get-Date | Get-Member Add*

   TypeName: System.DateTime

Name            MemberType Definition
----            ---------- ----------
Add             Method     datetime Add(timespan value)
AddDays         Method     datetime AddDays(double value)
AddHours        Method     datetime AddHours(double value)
AddMilliseconds Method     datetime AddMilliseconds(double value)
AddMinutes      Method     datetime AddMinutes(double value)
AddMonths       Method     datetime AddMonths(int months)
AddSeconds      Method     datetime AddSeconds(double value)
AddTicks        Method     datetime AddTicks(long value)
AddYears        Method     datetime AddYears(int value)

Passing a negative value to one of the Add* methods will result in subtracting that amount of time:

PS C:> Get-Date
Wednesday, 12 August 2015 2:37:23 PM

PS C:> (Get-Date).AddMonths(1)
Saturday, 12 September 2015 2:37:37 PM

PS C:> (Get-Date).AddMonths(-1)
Sunday, 12 July 2015 2:37:40 PM

Obviously you need to be very careful with specifying a path to a command like Remove-Item. I’ve left the -WhatIf switch on the example code above.

It’s easy to add something like this to a scheduled task to keep a log folder tidy.

Simple IP Range Scan using PowerShell

I had to jump on a bunch of remote servers today that are largely unmanaged by my department.

Since it was the first time I’d needed to deal with this job, and there was no prior documentation, I needed to run a basic discovery process to see which machines were on the network. In lieu of finding, downloading, and installing an IP scan tool, I decided to give it a go using PowerShell 2.0, which is what was installed on these servers.

The network range to be scanned was a simple, so what I tried first was this:

1..254 | ForEach-Object {Test-Connection -ComputerName "192.168.0.$_" -Count 1 -ErrorAction SilentlyContinue}

This will at least tell me which IPs are alive, but it won’t resolve those IPs to hostnames. I discovered that several other people had the same gripe as me.

Since Test-Connection uses WMI under the covers, I decided to give the WMI-based solution a go. A bit of tweaking, and it resulted in what I needed.

Here’s the code, a classic PowerShell one-liner:

1..254 | ForEach-Object {Get-WmiObject Win32_PingStatus -Filter "Address='192.168.0.$_' and Timeout=200 and ResolveAddressNames='true' and StatusCode=0" | select ProtocolAddress*}

And here’s what it outputs:


More Information – MSDN: Win32_PingStatus

Copy Active Directory Group Membership with PowerShell

If you need to copy AD group memberships from one user or computer account to another, you can do so with the following two lines of PowerShell.

This depends on the ActiveDirectory module being loaded, or auto-loaded in PS 3.0 or newer.

# Get the memberships from the source computer account
$memberships = Get-ADComputer source -Properties memberof | Select-Object -ExpandProperty memberof

# Apply the memberships to the destination computer account
Get-ADComputer destination | Add-ADPrincipalGroupMembership -MemberOf $memberships -Verbose -WhatIf

Copy the previous PowerShell command to the clipboard

I often have multiple PowerShell windows open; at least one for testing out commands, and then the ISE for writing scripts.

Here’s a quick one-liner to copy the previous PowerShell command to clipboard:

h -c 1 | select -exp commandline | clip

To elaborate on that, here’s the version that doesn’t use aliases:

Get-History -Count 1 | Select-Object -ExpandProperty CommandLine | clip

TechNet: Get-History