Powershell fun

PowerShell

Finding specific files

The Folder path in which you need to search
$Path = ‘C:\folder\path’

The Regular Expression you like to match
$RegEx = ‘[^-\w\.]’

The CSV File name
$OutCSV=’C:\temp\fileResults.csv’

Get-ChildItem -Path $Path -Recurse | Where-Object -FilterScript { $_.Name -match $RegEx } | Export-Csv -Path $OutCSV -NoTypeInformation

 

And YA! files modified in the last 24 hrs…

Get-ChildItem -Path $Path -Recurse | Where-Object -FilterScript { ((get-date) - $_.LastWriteTime).totalhours -le 24 } | Export-Csv -Path $OutCSV -NoTypeInformation

Finding Objects Properties

I used to use

$Object | FT *

to try and find properties to use, but easier is

$Object | get-Member

So much easier to see all an objects properties, and methods… and whatever 😀

There are obviously endless things you can do with powershell, with a syntax farrrrrrrrr more intuitive then BATCH ever was. That is really thanks to a lot of object operations relying on non other then regular expressions AKA RegEx.

RegEx is powerfull but also hard to grasp at times. Which funny enough has caused this blogger an unknown grief :P, though someone suggests not to use $ in between double quotes but for single quotes.. but it’s not “just that easy” as understanding the difference between using single quotes and double quotes is a big thing in PowerShell and understanding this as PowerShell as much as the RegEx underneith has it’s own special synatx on certain things and certain characters, so not only does PowerShell interpreter have it’s own parser with escape characters… So does RegEx so you have to know the info in the above link, as well as understand RexEx’s syntax, special characters, and escape characters specially when using operators like -like -match -contains or even -AllMatches…

Finding Files With Specific Characters

Outside of setting variables this really comes down to one line in the alternative bloggers script… line 8…

$item.Name | Select-String -AllMatches $UnsupportedChars

Nice, so simple, but as mentioned this breaks if you attempt just specific special characters, such as $ or ^ as these are special characters to RegEx and need to otherwise be escaped to be taken literally.

Now the script in and of itself is simple enough to accomplish a simple task.. to explain in better detail what I mean… here’s my list of files, NTFS accepts more strange characters then I first though, so users can really be creative…

clearly much more complex, so if I run his script what happens….

Now I commented out the line that actually renames the files, so you know, I don’t have to rename them to reset my test. That’s good, but even though he added the money sign in his blog to the “$UnsupportedChars” var, he didn’t actually add it in the final script, and what happens if you do?

ok so in this case, the -AllMatches worked without issues I suppose due to changing to single quotes. but what if we need to change it’s character, let’s copy one of his ifs that changes it and see how it responds…

The results…

hahahha that’s a lot of money, clearly the RegEx got confused when we specified $ in the -Replace operations… so as the source RegEx escape characters says, escape it with \…. lets do that…

Awesome it worked… but I don’t like edited a script every time I need a slight change in my search criteria…. let’s take his script… and well completely redesign it as if it were on Steroids!!!  Functions! – to reuse reusable code with returns! Write-Hosts and Read-Hosts for user interaction and input, and heck why not options on results! Export, List, rename sounds good! This along with some dependency checking! (Does the path exist? Do you have rights to modify?)… oh yeah and confirmation of action! 😛

This beast will need it’s own GitHub Repo… so… here it is!

OK… now that I’m actually writing this blog actively while I’m polishing my script.. I noticed my export section had a Try, Catch for the creation of the CSV file. Simple but this, in itself, is not actually checking if I have permission… it’s simply checking to see if the I/O operation is actually able to succeed… it may fail due to other reasons (store space for example). So how do I check to see if I actually have permission before continuing? Well check your group membership (along with your own SID) and check the files/folders Access Control Lists (ACLs), obviously.

So how do I get a workaround based machines local user group membership?

Googling I can find easy how to do this for a domain joined machine, which is fantastic if I wanted to validate the path on a network share where the ACE’s and ACL’s are all managed by domain SIDs. But this test and script is all on my “workgroup” machine… and finally after enough searching the holy grail!

Wasn’t done directly by Ed Wilson but close enough, it gave the the code I needed to check the members of the local admin group, but what I really need is simply to validate read/write permissions.. So I found this… and sadly it doesn’t understand the fully distinguished user name when using whoami as the user variable… and it can’t sense inherited permissions… only once I granted my akadmin direct read and execute permissions would the code work…

Like ughhhhhhhhhhh………

I’m gonna skip this in my script for now… rather work on getting export to work properly, and actually getting the rename code in when saying yes. :O.. will update this post when that’s done.

… and yet in my drunken’ stuper I decide to keep trying…

I think the only way is only to iterate through the local groups and verify users.. nope leaving this again, unless a better way comes about.

OK.. so I felt I was on to something…

figured there’s got to be an easy way to do the same cmd as whoami /groups in PowerShell right… wtf is going on here?!?! I thought PowerShell was the easier scripting language… man this is crazy. and this

Not even brah… THIS! This is getting close!

This is gon’ be fun! (Great Source)

$folder = "c:\temp"
$CP = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UGM = $CP.Identities.Groups | ?{$_.AccountDomainSid -ne $null}
$UGM = $UGM + $CP.Identities.User
$TPP = (Get-Acl $folder).Access
foreach ($P in $TPP.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]))
{
foreach ($UP in $UGM){if($UP -eq $P){Write-Host "We got permissions here!`n";$TPP | ?{$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -match $P}}}
}

Testing this out on my the account I added to a group worked just fine, but on my main machine it failed on me…

Poop, what happened… turned out my code filters the nitty gritty built in groups. By default all users are a part of these and aren’t generally used to define permissions (but can be, E.G Administrators). Since there was only one group my account was a member of the PS Object “$UGM” was not of a multi-value object aka an array object and doesn’t have an addition type method to complete the addition of the user SID, to the what should be array, as I have not defined it as such. So this is a good example to usually always define your variable types or fault to the problems they can inhibit. Also I was stunned I was not able to see the group SID when I called for all local groups, no matter how I called them, so doing the magic translate trick as the source above…

What the heck… None … didn’t even know such a group existed…

Code fixed by doing the following:

$folder = "c:\temp"

$CP = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UGM = @($CP.Identities.Groups | ?{$_.AccountDomainSid -ne $null})
$UGM = $UGM + $CP.Identities.User
$TPP = (Get-Acl $folder).Access 
foreach ($P in $TPP.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])) 
{
 foreach ($UP in $UGM){if($UP -eq $P){Write-Host "We got permissions here!`n";$TPP | ?{$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -match $P}}} 
}

After enough beers I got it to even let the user know if they are in the admins group but not elevated 🙂

$folder = "c:\temp\None"

$CP = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$UGM = @($CP.Identities.Groups | ?{$_.AccountDomainSid -ne $null})
$UGM = $UGM + $CP.Identities.User
if(([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){$UGM = $UGM + ($CP.Identities.Groups | ?{$_.Value -eq "S-1-5-32-544"})}

$TPP = (Get-Acl $folder).Access

foreach ($P in $TPP.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]))
{
    foreach ($UP in $UGM){if($UP -eq $P){Write-Host "We got permissions here!";$TPP | ?{$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -match $P}}}
}

foreach($pu in (Get-LocalGroup Administrators | Get-LocalGroupMember).SID){if($pu -eq $CP.Identities.User -And !([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){Write-Host "You are an admin, but you are not elevated..."}}

Fixing the CSV Export

So far I have not go the permission checking working just yet. I have however managed to fix the export-csv issue.. old export…

Yeah that System.Object[] isn’t very helpful..

based on this old code:

$shit | Select-Object Name, Directory, MatchedValue| Export-Csv -Path $CSVFile -NoTypeInformation

I was trying all these weird things to try n resolve it (bad ideas)…

a lot of extra code for not good results, I figured there had to be a better way… and sure enough there was… Source

which left me with this code:

 $BadFiles | Select-Object Name, Directory, @{Expression={$_.MatchedValue -join ';'}} | Export-Csv -Path $CSVFile -NoTypeInformation

which now provides this output:

Nice! Until the moment I realized, can user use ; in file names… Ahhh they can! shoot! So now my test files grows larger, but my script help up with very lil modification! first to add it in the list of searchable characters:

function GetChars()
{
    Write-host "Characters to search for !$'&{^,}~#%][);( " -ForegroundColor Magenta -NoNewline
    $UnsupportedChars = Read-Host
    if ( !$UnsupportedChars ){ $UnsupportedChars = "\]!$'&{^,+}~#%[);(" }
    return $UnsupportedChars
}

That was it! the list output is fine:

but the export not so much as I was using ; to join those items…

K well users can use a \ so let’s see if we can use that in the export, again easy code change..

$BadFiles | Select-Object Name, Directory, @{Expression={$_.MatchedValue -join ';'}} | Export-Csv -Path $CSVFile -NoTypeInformation

Let’s test…

Nice, also added =, and let’s also test for @…

easy got em… until I realized you can name a file with a backtack `

and if I search for it on it’s own no problem…

but if I try to extend the default list of characters, I can’t seem to get it to work, even if I specify an escape \ in my list… Woah! I randomly got it!

function GetChars()
{
    Write-host "Characters to search for !$'&{^,}~#%]`[)@=;( " -ForegroundColor Magenta -NoNewline
    $UnsupportedChars = Read-Host
    if ( !$UnsupportedChars ){ $UnsupportedChars = "\]!$'&{^,+}~#%[)@=;(``" }
    return $UnsupportedChars
}

Double backticks worked, what a random thing to try and it actually worked…

That’s it for tonight! I got pretty much every special character covered. 🙂

Next time I want to cover bulk confirmation with a report, since confirming every one can be annoying, but better to have confirmation then not!

PIssssss!!!!! Source of problem

Oi! I DID IT!!! YES!

From this…

        if(confirm $turd.name $turd.NameToChange)
        {
            Try { Rename-Item $turd.name -NewName ($turd.NameToChange) }
            Catch { Centeralize "Unable to change file's name"$turd.name "Red"}
        }
        else
        {
            Write-Host "Maybe Another time..."
        }

to this (after exporting to it’s own function though)…

        if(confirm $turd.name $turd.NameToChange)
        {
            
            $ThatOldName = $turd.FullName
            if (($ThatOldName).ToCharArray() -ccontains "[") {Write-host "We found a bad char";$ThatOldName -replace "\[", "`["}
            Try { Rename-Item -literalPath $ThatOldName -NewName ($turd.NameToChange) -ErrorAction Stop }
            Catch [System.UnauthorizedAccessException]
            {
                Centeralize "You do not have permission to change this file's name" "Red"
            }
            Catch 
            { 
                $ErMsg = "Unable to change file's name "+$turd.name
                Centeralize $ErMsg "Red"
                $ErrMsg = $_.Exception.Message
                Centeralize $ErrMsg "Red"
            }
        }
        else
        {
            Write-Host "Maybe Another time..."
        }
    }

I can’t believe it worked! wooo results from this:

To this!:

I can’t do much about files that already exists, besides people mentioing way to over-write existing files which I’m not coding for, nope sorry.

but so far so good. This script is awesome!

Today I discovered that Try-Catch only works on terminating exceptions…

Powershell ErrorAction

Sigh… This means I have to do additional coding if I want the script to get all items from main folder, even if there’s a permission restriction on a child folder.

Life’s rough!

Leave a Reply

Your email address will not be published. Required fields are marked *