Automatically move old photos out of DropBox with PowerShell to free up space

February 02, 2015 FoxDeploy

I love dropbox, we all love Dropbox, it even comes preinstalled on our phones!

Sadly, it can be irritating when you get notices like this on the desktop

Dropbox Nag Screen

I noticed one day that my DropBox camera roll folder was using up the vast majority of my free space, something like 60 or more percent! Since I’ve got a 9 month old, its no wonder. I am constantly recording pictures and videos of her, so it’s a matter of course.

If we could move all files older than three or four months out of the Dropbox ‘Camera Uploads’ folder, we could really free up some space! Additionally, I wanted to backup my files from DropBox onto another Storage Provider (OneDrive) as well as move a copy onto my backup drive. To solve all of these needs, I wrote a PowerShell script which can run as a scheduled task to handle moving the items out of the Camera Uploads folder. This can be run on any system that you’ve got the DropBox desktop client installed.

I even included a snazzy looking summary report which is displayed after the job finishes.

Simply download the .ps1 file, then modify the few highlighted lines below.

####User Params Here # / This should be the path to your Dropbox camera Uploads folder $cameraFolder = "C:UsersStephenDropboxCamera Uploads"

# / If you want to create a copy somewhere else, provide it here 
$SkyDriveFolder = "C:\Users\Stephen\SkyDrive\Pictures\Camera Roll"

# / Finally, files are MOVED from your Dropbox folder to this path 
$BackupFolder = "D:\Photos\Galaxy Note II"

# / Specify the maximum age of files in days here. Anything older than this is moved out of the $cameraFolder path 
$MoveFilesOlderThanAge = "-113"

# / Specify the path to an optional logo for your status report 
$logoPath = C:UsersStephenDropboxSpeakingDemoslogo.png"

\# / Specify the path to a CSS file $cssPath = "C:\Users\Stephen\Dropbox\Speaking\Demosstyle.css" ####End user params 

Save the code below as a Move-DropBoxFiles.ps1. Next, to setup a scheduled task using the below code. (Borrowed liberally from the King of Code himself, Ed Wilson, in this blog post on Scheduled Jobs.

$dailyTrigger = New-JobTrigger -Daily -At "2:00 PM"

$option = New-ScheduledJobOption -StartIfOnBattery StartIfIdle

Register-ScheduledJob -Name UpdateHelp -ScriptBlock \`

{c:pathtoMove-DropboxFiles.ps1} -Trigger $dailyTrigger -ScheduledJobOption $option 

Now, when this runs every day at 2:00 PM, you should see a PowerShell window popup momentarily, and then the files will be copied and moved according to the criteria you select. When the job is finished, you’ll see this nice looking HTML Status Page pop-up.

Automatic file move from DropBox report

Enjoy!

Full source here:

####User Params Here
# / This should be the path to your Dropbox camera Uploads folder
$cameraFolder = "C:UsersStephenDropboxCamera Uploads"
 
# / If you want to create a copy somewhere else, provide it here
$SkyDriveFolder = "C:UsersStephenSkyDrivePicturesCamera Roll"
 
# / Finally, files are MOVED from your Dropbox folder to this path
$BackupFolder = "D:PhotosGalaxy Note II"
 
# / Specify the maximum age of files in days here.  Anything older than this is moved out of the $cameraFolder path
$MoveFilesOlderThanAge = "-113"
 
# / Specify the path to an optional logo for your status report
$logoPath = "C:UsersStephenDropboxSpeakingDemoslogo.png"
 
# / Specify the path to a CSS file 
$cssPath =  "C:UsersStephenDropboxSpeakingDemosstyle.css"
####End user params
 
 
$backupFiles = new-object System.Collections.ArrayList
$itemCount = Get-ChildItem $cameraFolder |  Where-Object LastWriteTime -le ((get-date).AddDays($MoveFilesOlderThanAge)) | Measure-Object | select -ExpandProperty Count
 
Get-ChildItem $cameraFolder |  Where-Object LastWriteTime -le ((get-date).AddDays($MoveFilesOlderThanAge)) | ForEach-Object {
    $i++ 
    Write-Progress -PercentComplete (($i/$itemCount) * 100) -Status "Moving $_ ($i of $itemCount)" -Activity ("Backup up files older than " + ((get-date).AddDays($MoveFilesOlderThanAge)))
    Copy-Item -Destination $SkyDriveFolder -Path $_.FullName -PassThru
    $backupFiles.Add((Move-Item -Destination $BackupFolder -Path $_.FullName -PassThru | select BaseName,Extension,Length,Directory))
    Start-Sleep -Milliseconds 25
    }
 
$companyLogo = "<div align=left><img src=`"$logoPath`"></div>"
$header = @"
  
 $companyLogo
 <h1>File export from Dropbox Report</h1>
 <p>The following automated report was generated at $(Get-Date) and contains the $itemcount files which were older than 
$([math]::Abs($MoveFilesOlderThanAge)) days. <Br><Br>This backup job was executed on System: $($Env:Computername)</p>
 
 
 <hr>
"@
 
$post = @"
 <h3>These items were moved to <b>$BackupFolder</b> for archiving to Azure</h3>
"@
 
$HTMLbase = $backupFiles | ConvertTo-Html -Head $header -CssUri $CssPath `
                            -Title ("Dropbox Backup Report for $((Get-Date -UFormat "%Y-%m-%d"))") `
                            -PostContent $post
                                             
 
$HTMLbase | out-file $StatusReportPathDropboxBackup_$((Get-Date -UFormat "%Y-%m-%d"))_Log.html 
 
& $StatusReportPathDropboxBackup_$((Get-Date -UFormat "%Y-%m-%d"))_Log.html 

CSS File Removed

The Referenced Css File was Zen Theme from CSS Garden.

Continue Reading...

Impractical One-liner Challenge #2

January 23, 2015 FoxDeploy

It’s back, your favorite Friday PowerShell diversion!

The get-date command has a useful method called GetDateTimeFormats() that lists all of the available date-time formats available on your system, suitable for you to find just the right date format to fit your needs.  However, actually using the output can be difficult, as you must use the following syntax, (Get-Date).GetDateTimeFormats()[\] and picking the right one from a huge non-numbered list can be challenging.

Your Mission

Display all of the dateTime formats available, along with the index position of each in a graphical GUI user interface for the user to pick the format they desire, then use that selection to display a tip to the user on how to get that particular format.

Rules

  • Try to avoid using Semicolons.  If you have to use one, explain it.
  • You are encourage to use backticks and PowerShell next-line detection (brackets, pipes) to wrap your code in to a readable one-liner
  • Interpret GUI and ‘display a tip’ however you like
  • No prizes, no whining!
Continue Reading...

SCCM 2012 Log File Quick Reference Chart

January 21, 2015 FoxDeploy

Depicts an image saying 'Scripting System Center Configuration Manager'

This post is part of the ‘Scripting SCCM’ series on FoxDeploy, click the banner for more!


Jumping back into the SCCM Saddle, I noticed that I could really benefit from a printable SCCM reference guide.  I decided to whip one up, using the data from the Technet page.

Print up this full-sized .webp or download the PDF Below.

Printable SCCM 2012 Log File Chart and Reference Guide Printable SCCM 2012 Log File Chart and Reference Guide

SCCM 2012 Logs Chart

Continue Reading...

Walkthrough Part two: Advanced Parsing with ConvertFrom-String

January 13, 2015 FoxDeploy

In our previous post we used the tried and true old school method to convert data from the console or logs into a PowerShell object.  It works but it isn’t very much fun and by and large you have to write a lot of code to do it.

I’ve dug under the covers, thanks to these excellent resources (at the bottom of this post!) and have now come up with a method to parse files using PowerShell Version 5’s awesomely powerful new ConvertFrom-String cmdlet.  This tool is based on FlashExtract, which is a Microsoft Labs creation that originally showed up in Excel under the FlashFill and FlashConvert tools, which let you highlight some sample data and watch as a machine learning creates regex string extraction tools for you.  Automatically.

I’m not sure if is cool or if it is scary.  It’s definitely cool at least!

This new approach drops down from more than 60 lines of code to only 13 to parse the data.  But the craziest thing about is how the code looks and works.

Machine learning will take our jobs

Will it? Maybe…probably. Previously you needed Exchange Admins to monitor all of an org’s exchange servers, and infrastructure admins to watch over the datacenter, plus guys to swap tapes, make custom internal apps and things like that. Now you can do all of that through AWS or Azure, so presumably at least one guy is looking for a job now. Probably the one who tended the printers and tape drives. Most of the apps we’d make internally have now found a ‘good-enough’ alternative online with SaaS.

My personal plan is to try to be the guy who automates the last mile and glues all of the systems together, so hopefully I’ll have a job as long as possible.
/rant

Our old approach worked, but was pretty hard. Let’s make things so, so much easier using PowerShell version 5.0’s awesome new ConvertFrom-String cmdlet. Along with this, we’ll use a new example to make things extra obvious.

Our new approach

As I mentioned before, our new tool of choice ConvertFrom-String is built on a product from Microsoft Labs called FlashExtract. You can see it in GUI form in action on this video here. ConvertFrom-String on its own does a great job of trying to map columns from an input log or string, but if you have an entry that spans many lines, you’ll want to create a template file that you provide to CFS using the -TemplateContent or -TemplateFile param. You make one of these files by taking a snippet of your usual input data, then mark it up using special characters to highlight which fields we care about and want to extract. Then, we provide this template to ConvertFrom-String and experiment with it to see how many example rows we need to provide in order to let ConvertFrom-String learn about our input file.

Syntax {VariableName*:Values} - Each of these is a parent object, things that come behind it are properties {?VariableName:Values} - this Might be {!VariableName:Values} - This is NOT what I want

Notice that we DON’T use a normal PowerShell variable heading. I know, that’s confusing, let me show you an example.

Here is our super basic HTML input file.

</table> </body> </html> Our next step here is to take a row or two that we care about, and mark out our variables. Taking a glance at the file, it is pretty obvious that our Company records are in the left column, while the AccountNumber is in the right. We have to teach this to the computer though. [![CFS_filePreview](../assets/images/2015/01/images/cfs_filepreview.webp)](../assets/images/2015/01/images/cfs_filepreview.webp) ### Making a template Now, we use the tags above to mark out our Company name like this {Company:} and the AccountNumber like so {AccountNumber:}. We'll take a snippet of our input doc then 'highlight' a few of the entries we care about using the tags. We'll store this in a here-string called $template (but you could save it as a template file as well and use the -TemplateFile param instead) ```powershell $template = @'
CompanyAccountNumber
AppleCorpABC
BananaIncDEF
CherryCoGHI
DaquiriInc123
EggPlant456
{Company\*:AppleCorp}{AccountCode:ABC}
'@ ``` See what we're doing? We're marking out with one sample row the data we care about, the Company with an asterisk, to signal to FlashExtract to return multiple instances of these. If we left off the \*, we'd get back the first match, which would literally be the word Company from the header of the table. Adding a star gives us back multiple matches. Let's see what happens when we run this through ConvertFrom-String. A note: I like to use this syntax to get an output of how many objects were detected, and then leave me with a string I can play with. ```powershell ConvertFrom-String -InputObject $import -TemplateContent $template -OutVariable TestMePlz | Out-Null "Parent Objects found $($testmePlz.Count)" "-Child objects found $($testmePlz.Company.Items.Count)" $TestMePlz | select Company ``` [![CFS_01](../assets/images/2015/01/images/cfs_01.webp)](../assets/images/2015/01/images/cfs_01.webp) [![CFS_Output01](../assets/images/2015/01/images/cfs_output01.webp)](../assets/images/2015/01/images/cfs_output01.webp) We see that it captured the name of each company ($testmeplz.Company) but it didn't grab the AccountNumber! Not bad for very little work! If we now add a bit more detail to our template file, we can hopefully narrow things down and grab the rest of the output. Let's try adding an extra row to our template to give it another example and try and teach the system. While we're at it, we don't want ConvertFrom-String to return the Header row of our table (we don't want the values of Company=Company and AccountNumber=AccountNumber) so we'll add an exclamation point to the value. We're doing both with these two lines highlighted below. ```powershell $template = @'
{!Company\*:Company}AccountNumber
{Company\*:AppleCorp}{AccountNumber\*:ABC}
{Company\*:BananaInc}{AccountNumber\*:DEF}
'@ ConvertFrom-String -InputObject $import -TemplateContent $template -OutVariable TestMePlz | Out-Null "Parent Objects found $($testmePlz.Count)" "-Child objects found $($testmePlz.Company.Items.Count)" $TestMePlz | select Company ``` ![CFS_Output02](../assets/images/2015/01/images/cfs_output02.webp)] Better, now we're getting back at least the first three companies AND we have the accountnumber too! We also don't have an that pesky incorrect result of 'Company/Accountnumber' crapping things up anymore either. **Calculated Properties will save us all!** In case you haven't noticed thus far, I'm mapping a custom object using calculated properties to get some more meaningful results. I'm doing this because currently in WMF 5.0 preview, there is a LOT of debug info returned when you run ConvertFrom-String, including the column 'ExtentText' which is great for troubleshooting but not needed for most use cases. I suspect (but have no secret knowledge) this will change as time passes, because CFS also doesn't do a great job of mapping objects back for you, which I suspect will also be remedied in a future version. In any case, FlashExtract/ConvertFrom-String will do it's best to assign leaf/branches as makes sense for your input file. As you can see below, we get back a Company.Value property, while the AccountNumber becomes a sub-property @ Company.AccountNumber.Value. ```powershell $TestMePlz | Select-Object @{Name=‘Company‘;Expression={$\_.Company.Value}},@{Name=‘AccountNumber’;Expression={$\_.Company.AccountNumber.Value}} ``` [![CFS_03](../assets/images/2015/01/images/cfs_03.webp)](../assets/images/2015/01/images/cfs_03.webp) We're running into an issue here…we're only getting back 3 of our five inputs! Is there any difference between these last two (Daquiri and Eggplant) compared to the others? We could look at the debug output for more info. ```powershell DEBUG: Property: Company Program: ESSL((EndsWith(Dynamic Token(</td>)(), ALL CAPS(\\p{Lu}(\\p{Lu})+), Dynamic Token(</tr>)(</td></tr>))): 0, 1, ...: Dynamic Token()()...Alphabet(\[\\p{Lu}\\p{Ll}\\-.\]+), Dynamic Token()(), ALL CAPS(\\p{Lu}(\\p{Lu })+), 1 + Camel Case(\\p{Lu}(\\p{Ll})+)...Dynamic Token()(), ALL CAPS(\\p{Lu}(\\p{Lu})+), Dynamic Token()(</ td></tr>), 1) ------------------------------------------------- Property: AccountNumber Program: ESSL((EndsWith(Dynamic Token()(), ALL CAPS(\\p{Lu}(\\p{Lu})+), Dynamic Token()(</td></tr>))): 0, 1, ...: Camel Case(\\p{Lu}(\\p{Ll})+), Dynamic Token(</td>)()...ALL CAPS(\\p{Lu}(\\p{Lu})+), Dynamic Token(</tr>)(</td ></tr>), 1 + Camel Case(\\p{Lu}(\\p{Ll})+), Dynamic Token(</td>)(), ALL CAPS(\\p{Lu}(\\p{Lu})+)...Dynamic Token(</tr>) (</td></tr>), 1) ------------------------------------------------- ``` Uh…if you can read that you're a better man than me. One thing jumps out though, the accountNumber for the other two entries are numeric, not alphabetical. We basically told FlashExtract that 'gosh, we must only want letters for the Account field'. Let's add one more entry to our template and see what happens. Here's our complete code for this example, including the debug output and the item mapping: ```powershell $template = @' </table> '@ ConvertFrom-String -InputObject $import -TemplateContent $template -OutVariable TestMePlz | Out-Null "Parent Objects found $($testmePlz.Count)" "-Child objects found $($testmePlz.Company.Items.Count)" $TestMePlz | Select-Object @{Name=‘Company‘;Expression={$\_.Company.Value}},@{Name=‘AccountNumber’;Expression={$\_.Company.AccountNumber.Value}} ``` [![CFS_FullReport03](../assets/images/2015/01/images/cfs_fullreport03.webp)](../assets/images/2015/01/images/cfs_fullreport03.webp) So, it's a tricky concept, but look at how much less code this used than the previous functional approach. Imagine if you had a very complex list of addresses, license keys and things like that to parse, and I think you can see why this is a worthwhile endeavor. Now when you get a task to import data, it can be as easy as sitting down with a highlighter, marking out the columns and rows, then just adding a few {VariableName} tags to your document. In my mind this is one of those killer-app features that PowerShell offers which really has no comparison in other scripting languages. ### Revisiting our previous example To refresh our memory, we had an input file like this: ``` \\\\fox\_app2.foxdeploy.com\\apps\\FinanceTeam RW AMER\\Domain Admins RW AMER\\FinanceTeam Write RW AMER\\FinanceTeam Owner RW AMER\\Domain Admins RW AMER\\FinanceTeam Write\_permissions RW AMER\\FinanceTeam Write\_limited \\\\fox\_app2.foxdeploy.com\\apps\\Hyperion RW BUILTIN\\Administrators RW AMER\\Domain Admins R Funky Town EW Hamster World \\\\fox\_app2.foxdeploy.com\\apps\\IAHI doc RW BUILTIN\\Administrators RW AMER\\Domain Admins R Everyone can have spaces in the name \\\\foxland\\Meow PO ThisMakesSense ``` Using the new tricks we've learned with ConvertFrom-String, we can reduce our code to input from 60 lines, down to this! ```powershell $Template = @' {ShareName\*:\\\\fox\_app2.foxdeploy.com\\apps\\FinanceTeam} {perm\*:\*} {user:\*} {ShareName\*:\\\\fox\_app2.foxdeploy.com\\apps\\\*} {perm\*:RW} {user:BUILTIN\\Administrators} {perm\*:RW} {user:AMER\\Domain Admins} {perm\*:\*} {user:Everyone} {ShareName\*:\\\\\*} '@ $testme = ConvertFrom-String -InputObject $filecontents -TemplateContent $Template -OutVariable TestMe "Parent Objects found $($testme.Count)" "-Child objects found $($testme.ShareName.Items.Count)" $testme | Select-Object @{Name=‘Sharename‘;Expression={$\_.ShareName.Value}},@{Name=‘User’;Expression={$\_.ShareName.Items.User}},@{Name=‘Perms’;Expression={$\_.ShareName.Items.Perm}} ``` ![](../assets/images/2015/01/images/cfs_output03.webp) This is getting to be so easy! Fully resolving all of our objects, with less than a quarter of the code. **AWESOME!** **References** Ted Hart - PowerShell Team Blog - _ConvertFrom-String: Example-based text parsing_ http://blogs.msdn.com/b/powershell/archive/2014/10/31/convertfrom-string-example-based-text-parsing.aspx Francois-Xavier Cat - LazyWinAdmin.com _PowerShell - ConvertFrom-String and the TemplateFile parameter_ http://www.lazywinadmin.com/2014/09/powershell-convertfrom-string-and.html Tobias Weltner - PowerShell Summit Europe 2014 - _Sophisticated Techniques of Plain Text Parsing_ [YouTube - Sophisticated Techniques of Plain Text Parsing](http://www.youtube.com/watch?v=Hkzd8spCfCU&index=5&list=PLfeA8kIs7Coehjg9cB6foPjBojLHYQGb_)
{!Company\*:Company}AccountNumber
{Company\*:AppleCorp}{AccountNumber\*:ABC}
{Company\*:BananaInc}{AccountNumber\*:DEF}
{Company\*:DaquiriInc}{AccountNumber\*:123}
Continue Reading...

Walkthrough - Parsing log or console output with PowerShell

January 05, 2015 FoxDeploy

BlogGraphic-ParseData

As I go from project to project, inevitably a question like this one arises:

Stephen, I know I COULD do this by hand, but surely PowerShell can do it for me!  Help!

In this post I’ll walk you through a real world example of how to parse the output of a non-PowerShell command and convert it into PowerShell objects we can work with to export reports, run SQL queries, or to do literally anything under the sun.

If you’ve ever needed to parse log or batch file command output and have slaved at it, hopefully this approach makes things a little easier for you.

This is part of a two-part series,  in this post we’ll go step-by-step through the traditional, tried-and-true method of parsing output.  The next post in the series is all about using Convert-FromString to parse input instead, a powerful new addition to PowerShell in version 5.0.

The Process

First of all, you must understand that there is NO-MAGIC to this approach.  We need to look at our input data, use our minds and think of how to tell PowerShell to parse things.

The method we’ll use to approach this task is pretty simple, and can be summed up in the following steps:

  1. Before starting, let’s not be too fancy!  Instead of trying to do this in the pipeline, make thing simple.  Export the results of whatever log or command we need to work with into a txt file first.  Later we can worry about making a slick one-liner to do this whole process.
  2. Define the objects.
    • Notable Characteristics?  e.g. does a new object always begin with a share path ( for example ‘\\server\share’ would mean that a new item always starts with a double-backslash)
  3. Define our properties
    • Notable Characteristics? e.g. do our properties always start with white space or a ‘*’ character.
  4. Make liberal use of Write-Debug to work with a single object
  5. Once this single object works, attack the list en masse!

 

Understanding Our Target

This is a real world example, that came to me by way of a friend of mine–the guy who got me started with PowerShell in the first place–Jason Roth.

In our task, we had a very, very long list of shares in the environment and the share permissions on each.  We needed to itemize who had which permissions, and didn’t want to spend all day doing so.  Furthermore, no utility that existed already could do this for us.  However, if we could parse the results out into a format PowerShell could work with, we were in the clear!

We’re going to follow our own advice and not try to parse this stuff in the pipeline.  Instead, we just outputted to a .txt file using the well-known redirector character >

Here’s an example of what the output looked like.

\\\\fox\_app2.foxdeploy.com\\apps\\FinanceTeam
  RW AMER\\Domain Admins
  RW AMER\\FinanceTeam Write
  RW AMER\\FinanceTeam Owner
  RW AMER\\Domain Admins
  RW AMER\\FinanceTeam Write\_permissions
  RW AMER\\FinanceTeam Write\_limited
\\\\fox\_app2.foxdeploy.com\\apps\\Marketing
  RW BUILTIN\\Administrators
  RW AMER\\Domain Admins
  R  Everyone
\\\\fox\_app2.foxdeploy.com\\apps\\HR doc
  RW BUILTIN\\Administrators
  RW AMER\\Domain Admins
  R  Everyone

Nothing too tricky, fairly standard Permissions listing of the ACLs on these shares.  At this phase, we should think of what we’d like the output to be.  In my case, we wanted something like this.

The result of our parsing will be PowerShell objects with a ShareName property containing the name of the Share.  Each object will contain properties with the permissions broken up by each user, with a $user and $permissionLevel property.  Presented in table form:

Step

Example

Characteristic

    1. Define the Object

Each share

Start with a '\\'

    1. Define the properties

Each user and their permissions

Will be in-between each object (will find them between '\\')

To illustrate how we’ll do this, let’s draw a diagram of how we’ll interpret these fields from our input object.

parsin02

Everything that occurs between each instance of the ‘\’ will be considered properties of a single object.

Breaking out the objects

Let’s first load our object into a variable using get-content.

$fileContents = Get-Content C:\\temp\\acl.txt

Now, to dig in by breaking our list apart into items. We can see that every set of double backslash indicates the start of a new share.

parsin03

We can use the select-string command here to get instances of the double backslash pattern. Running this on our $filecontents gives us.

parsin04](../assets/images/2015/01/images/parsin04.webp) Use -SimpleMatch to keep PowerShell from treating the search query as Regex. ‘\\’ in regex would be a single escaped backslash character.

 

If we explore into the properties of the output of select string, we’ll see we now have a number of useful properties, including the line number on which this match occurred.

We’ve also completed step 1, we have our objects, the names of the shares, so we’ll go ahead and save that into its own variable, for use later.

 $sharename = $\_.Line

To define our properties, we’ll need to loop within the text between all of these objects, so we’ll do that in the next part. Your code should now look something like this.

$fileContents = Get-Content C:\\temp\\acl.txt $fileContents | Select-String -pattern "\\\\" -SimpleMatch | ForEach-Object { $sharename = $\_.Line } 

Digging into the objects

The way we’ll attack this further is to use the LineNumber of where our match occurred to grab all of the text between this and the next match. There are a number of ways to do this, but I like to use our section delimiting characters (the ‘\’) as anchor points. Our next step will be to grab all of the occurrences of delimiters and find the one which is closest to the beginning of this section:

 $startOfThisSection = $\_.LineNumber $startOfThisSection = [int]$startOfThisSection- 1

#Get the line number of all instances of '\\\\' 
$AllInstancesOfDelimeter = $fileContents | select-string -Pattern "\\\\" -SimpleMatch | select -expand LineNumber

#Get the closest '\\\\' to the beginning of my config 
$endOfThisConfigSec = ($AllInstancesOfDelimter | ?{ $\_-ge (($startOfThisSection)+1) })[1].LineNumber

At this point, we’ve got our input broken into three objects, so we’ll need to delve into our object using the Write-Debug command to set a break point. We’ll add a Write-Debug right after the above codeblock, and then set our console $DebugPreference = ‘Inquire’.

Now, to test if this works, we’ll need to direct our code to get the lines starting with the ‘\’ line of this section, but omit the first line(because that line is returned in this $match object, which we’re using for the name of our $sharename), then grab all lines between the $startofThisSection and the next instance of the ‘\’ delimeter. We’ll use array subindexing ($array[$arrayItem] ) to grab the particular entries.

$filecontents = $filecontents.Split("\`n")

$fileContents | select-string -Pattern "\\\\" -SimpleMatch | % {

$permissions = @() $sharename = $\_.Line $startOfPermissionConfig = $\_.LineNumber 

#"Range begins on line $startOfPermissionConfig" 
$startOfPermissionConfig = [int]$startOfPermissionConfig - 1

#Get all instances of '\\\\' 
$endOfConfigSection = $fileContents | select-string -Pattern "\\\\" -SimpleMatch | select LineNumber -expand LineNumber

#Get the closest '\\\\' to the beginning of my config 
$endOfThisConfigSec = ($endOfConfigSection | ?{ $\_-ge (($startOfPermissionConfig)+1) })[1].LineNumber

#Get all lines from $startofPermissionConfig to 
$endOfThisConfigSection $ThisSetOfPermissions = $fileContents[([int]$startOfPermissionConfig..([int]$endOfThisConfigSec-2))]

#if specified, grab the defaultrouter $ThisSetOfPermissions = 
$ThisSetOfPermissions | select -Skip 1

Write-Debug "Break into code to test the value of \`$sharename and \`$ThisSetOfPermissions?"

} 

Using Write-Debug to suspend our code, Matrix style

Now, when we run our code, we’ll be prompted by line 23 to Halt, Continue or Suspend this command.

Using Suspend while in a code loop is awesomely powerful! It allows you near Neo-level powers to jump into the command line and access the $_ object in real time, and see which objects exist in the pipeline. Instead of assigning an outside object the values of a point-in-time slice of your script, you can play with the results in real time!

[caption id=”attachment_5130” align=”alignnone” width=”500”]tumblr_lqmd84dp741qglnd4o1_500 How it feels to ‘pause time’ via Suspend

PowerShell v5 preview allows you to jump directly into debug mode while using the ISE by hitting  Control+B, so you don’t even have to set a Write-Debug breakpoint.  However, for ease and backwards compatibility, I like to leave Write-Debug statements liberally left in my code, which I can access using either the -Debug switch on a function or bound script, or by setting my $DebugPreference manually.  There are nearly limitless options to the troubleshooting and outcomes you can create when using Suspend.

When you run code with a $DebugPreference set to Inquire, you’ll get a nice prompt and console output with the message you left behind.  Save your future self from confusion and make sure to mention which variable you should test at this point in time.

parsing05

So, to check our values of $sharename and $ThisSetOfPermissions.

parsing06 See line 2 with the » mark? You’ll notice that you’re in a suspended command line when you see the Console Prompt icon change into a ‘»’ character.

Alright, the final step now is to iterate through our $ThisSetOfPermissions and assign each of these lines to be a new PowerShell custom object. We’ll collect all of these together into a $permissiosn array and then publish them as one $sharename object with a mult-valued $permissions property

Assigning Properties

Now that we know our $TheSetOfPermissions will contain the Permissions for the Share, we just need to iterate through them. A single $permission will look like this.

RW AMER\\Domain Admins

Working in the Suspended command line makes this very easy to hack apart our entries and assign them to variables.  Here’s the code, and then I’ll talk you through what it does:


ForEach ($permission in $ThisPermissionsList){ 
  write-debug "Experiment with \`$permission to split and assign the entries on the line" 
  $access =$permission.Trim().Split(' ')[0] 
  $user   =($permission.Trim().Split(' ') | Select -skip 1) -join ' ' 
  $permissions += [pscustomobject]@{User=$user;Permissions=$access} 
} 

We need to get rid of the leading spaces with a .Trim() sub-expression method call and then use .Split(‘ ‘) to split on spaces, and set the first value that we get back as our $access object (line 3).  Then, we’ll collect all of the remaining properties from the .Split(‘ ‘) and join them with a ‘space’ and assign that to a $user object (line 4).  Finally, we’ll collect and add them both to a $permissions object that reflects all of the $permission entries in $ThisPermissionsList.

Wrapping it up

When we’re done with the permissions loop above, we’ll collect all of them into another Custom Object, this one having a $sharename, and $permissions property.

 $permissions += [pscustomobject]@{User=$user;Permissions=$access} 

There is a pitfall you would run into with this particular example.  When you get to the last permission entry and there are no ending entries in the list (e.g. when you get to the last entry and it goes to look for the next delimiter after itself, there won’t be one) the code would dump out on you and give you a mangled final object  that contains every object in the list, with bad formatting. To get away from that, I added an extra piece of logic on line 20, that if this is the last delimiter in the list, then grab every non-whitespace line in the remainder of the list and return that for $ThisPermissionsList.

The Code

$filecontents = $filecontents.Split("\`n")

$fileContents | select-string -Pattern "\\\\" -SimpleMatch | % {

$permissions = @() $sharename = $\_.Line #<##experimental zone #Get Starting line of Permission Pool 
$startOfThisSection = $\_.LineNumber

#"Range begins on line $startOfThisSection" 
$startOfThisSection = [int]$startOfThisSection - 1

#Get all instances of '\\\\' 
$allInstanceOfDelimiter = $fileContents | select-string -Pattern "\\\\" -SimpleMatch | select -expand LineNumber

#Get the closest '\\\\' to the beginning of my config 
$endOfThisConfigSec = ($allInstanceOfDelimeter | ?{ $\_-ge (($startOfThisSection)+1) })[1]

if ($endOfThisConfigSec -eq $null) {$endOfThisConfigSec = $fileContents.Count}

#Get all lines from $startOfThisSection to 
$endOfThisConfigSection $ThisSetOfPermissions = $fileContents[([int]$startOfThisSection..([int]$endOfThisConfigSec-2))]

#We need to skip the first entry, to ensure that our sharename isn't included in the results 
$ThisSetOfPermissions = $ThisSetOfPermissions | select -Skip 1

Write-Debug "Break into code to test the value of \`$sharename and \`$ThisSetOfPermissions?"

ForEach ($permission in $ThisSetOfPermissions){ Write-Debug "Experiment with \`$permission to split and assign the entries on the line" 
$access =$permission.Trim().Split(' ')[0] 
$user =($permission.Trim().Split(' ') | Select -skip 1) -join ' ' 
$permissions += [pscustomobject]@{User=$user;Permissions=$access} }

[pscustomobject]@{ShareName=$sharename;
  Permissions=$permissions} }

The Results

parsing07

I hope you guys enjoyed this walk-through and that it helps you the next time you have to parse output from a non-PowerShell command, or are expected to flex your PowerShell wizardry and parse plaintext.

Continue Reading...

Achievement Unlocked - Microsoft MVP for 2015!

January 02, 2015 FoxDeploy

Dear Stephen Owen,  Congratulations! We are pleased to present you with the 2015 Microsoft® MVP Award! This award is given to exceptional technical community leaders who actively share their high quality, real world expertise with others. We appreciate your outstanding contributions in PowerShell technical communities during the past year.  Also in this email: About your MVP Award Gift How to claim your award benefits Your MVP Identification Number MVP Award Program Code of Conduct The Microsoft MVP Award provides us the unique opportunity to celebrate and honor your significant contributions and say "Thank you for your technical leadership."

Wow, talk about feeling blown away.  Back in September I received an e-mail stating that I’d been nominated to become an MVP, and since that moment I’ve felt that I’ve been walking on air.

I had a few career goals over my lifetime, from becoming a consultant and earning my MCSE, to eventually becoming a technical writer and trainer, and possibly even being as bold as to strive to become an MVP.  I never in my wildest dreams thought that it might happen to me!

Since I heard about the nomination back in September I’d had this cautiously optimistic feeling about it, not wanting to tell anyone but my wife, parents and a trusted few for fear of somehow spoiling it.  I knew there’d be a fierce vetting process to go through, and had heard that I’d have to provide hard numbers to back up the various things I’ve done in the community, so I knew I had my work cut out for me.

When I found out in early December that I would get the news on January 1st, I knew that this would be in the back of my mind through the whole holiday season.  I made sure I didn’t let it consume me, but I will admit to being so excited on New Year’s Eve that I practically couldnt sleep.  Getting a fever and a cold didn’t help things either!

So much F5-ing on Twitter

On New Year’s Day,  My wife and I were nursing colds trying to keep from getting our ChildItem from getting sick too, enjoying Theraflu and hot chocolate and winding down from the Holidays.

I should mention that I was also checking my Twitter constantly.  I saw my PowerShell friends overseas writing about their awards, the venerable folks in the Asia region talking about theirs.  I even saw a blog post on the SE Asia MVP site saying that all notices had been sent, congrats to the winners.  While I watched, Jan Egil Ring got his award, and then many others, and I felt my hopes rise, then fall as the tweets died out.  Someone mentioned that they got their notice at 8:30 AM in their local time, so I started thinking maybe it didn’t happen.

My wife told me I was letting it get to me and that I should just turn my ringer on and leave the phone in the bedroom and help her get the house straightened up (she was right, we were taking down the Christmas tree and there I stood unmoving for a few moments with Christmas lights and tinsel held in one hand and my phone in the other, furiously refreshing my GMail!)  I agreed and left my phone as she recommended.

We went back to playing with our daughter and taking down the Christmas decorations, as we played one last Pandora Christmas Playlist and had our cocoa, and tried to put feelings of disappointment aside.

Thirty minutes later, we heard the special ringer I set for Gmail messages containing the words ‘MVP’ go off!  I practically sprinted across the house to grab my phone!  I virtually slid from the door of the bedroom over to the bedside table, I don’t think my feet even touched the ground!  I snatched my phone up, fogetting my phone unlock code over and over in my haste.  When I finally controlled my thumbs again, I  saw the headline of the message

And we started dancing!  Our colds forgotten, we danced around the room and then went out to celebrate at the steak house!  We were on top of the world while we were out, spirits high and our minds clouded with robitussin…and then…our flu medicine wore off.  So what if I feel asleep in my mashed potatoes, woo-hoo!  A little bit of aus jus in my hair is a small price to pay!

Thank you every one!

I want to thank the person who nominated me, the folks who’ve read my blog, my family and coworkers for tolerating me talking about it relentlessly, and the incredible PowerShell community.  I may be prejudiced, but I swear that I’ve met some of the best people I know through my PowerShell and System Center connection to my peers in the Atlanta area.

Thank you all, I am very honored and deeply humbled to be chosen, and will strive to work ever harder for you all in this New Year!

Become A MVP

Continue Reading...

Microsoft MVP

Five time Microsoft MVP, and now I work for the mothership


Need Help?

Get help much faster on our new dedicated Subreddit!

depicts a crowd of people in a night club with colored lights and says 'join the foxdeploy subrreddit today'


Blog Series
series_sml_IntroToDsc
series_sml_PowerShellGUI series_sml_IntroToRaspberryPi Programming series_sml_IntroToWindows Remote Management Series The Logo for System Center Configuration Manager is displayed here Depicts a road sign saying 'Learning PowerShell Autocomplete'




Blog Stats