PowerShell Beautifier
Now on GitHub: https://GitHub.com/DTW-DanWard/PowerShell-Beautifier
Formatting Matters
Tabs or spaces; spaces or tabs? If spaces, how many? We sure do take whitespace seriously. But when writing ‘commit-worthy’ PowerShell code, there’s more than just whitespace to think about. Shouldn’t you use cmdlet names instead of aliases? And shouldn’t you have correct casing for cmdlets, methods and types?PowerShell Beautifier is a PowerShell command-line utility for cleaning and reformatting PowerShell script files, written in PowerShell. Sure, it will change all indentation to tabs or spaces for you - but it will do more than just that. A picture is worth 1KB words; here’s a before/after showing all types of changes including spaces & tabs:
Here's a simpler pic focusing on the alias-replacement and casing changes:
The PowerShell Beautifier makes these changes:
- properly indents code inside {}, [], () and $() groups
- replaces aliases with the command names: dir → Get-ChildItem
- fixes command name casing: get-childitem → Get-ChildItem
- fixes parameter name casing: Test-Path -path → Test-Path -Path
- fixes [type] casing
- changes all PowerShell shortcuts to lower: [STRING] → [string]
- changes other types (if in memory): [system.exception] → [System.Exception]
- cleans/rearranges all whitespace within a line
Now on GitHub: https://GitHub.com/DTW-DanWard/PowerShell-Beautifier
Nice Script - thanks!
ReplyDeleteI get a lot of requests for such a script so it's nice to have one to recommend.
Looking forward to V2.
Please add the ability to insert a new line for every line of the script. Will make them more readable
ReplyDeleteTry running it on InvokeSqlQuery.psm1 from here: http://powershell4sql.codeplex.com/downloads/get/125089
ReplyDeleteThe output at the start of Invoke-SqlQuery function gets all screwed up.
Cheers,
Andrew
WOW! Holy cow, what is going on there?
DeleteThank you for the heads up, Andrew. I tested this on a number of scripts I found on the internet but clearly not this one. I'm not sure what's going on; clearly sets of characters are being removed from the stream somehow. I will take a look into it soon...
Thank you, I'm checking here for the updates from time to time =)
DeleteAndrew.
Ran it on two of my files, both with very similar formatting. The first file ran fine, came out perfect. Ran it on the second, smaller file, and suddenly pieces of words were missing, variables go cut in half, strings were left open, it looked like someone went in there and tried to break it in every way possible.
ReplyDeleteI can't upload the files to show, as they are work-related, but I was surprised at the difference between two such similar files.
Unfortunately I've been crazy busy the past few months (working mostly 6 and 7 days a week) and haven't had the chance to look into this. I was able to reproduce the issue with the Invoke-SqlQuery file, which was surprising as I tested this script on many (hundreds) of script files before releasing it. I haven't looked closely yet but I wonder if it's choking on weird character, a Unicode space or some Unicode character that uses three bytes or more. I think all the Unicode files I tested only contained double-byte characters.
ReplyDeleteTry saving your script file as an ASCII file then run it through the pretty printer.
Darn, that didn't work.... had the same output.
Deletethanks for the quick response though.
Oh actually, I think I found the issue. For some reason my text editor only added a LF character whenever I hit return int he second file, instead of giving me a full CR/LF. That would most likely cause it!
Delete(weird text editor?)
AH now that sounds like that could be it. I haven't looked at the code in awhile but I know reads from the source as a big byte array and writes to the output. The index it uses to walk through the source array uses the actual length of the items on the line BUT I don't think it reads the end of line characters individually and checks the length - it probably assumes the length is 2. If so, it adds 2 to the source index, even though it should be adding 1, which means it's skipping ahead of legitimate content, which is why the content is lost when it writes the updated file.
DeleteTry taking the source file and re-saving it in a editor that forces the CR/LF to be added. Just to be sure, diff the before/after files to make sure the LF has been added. Once you are sure they are present, run it through the cleaner. Fingers crossed.
If this is it, I'll change the code to explicitly check the length of the newline but at least we'll have this workaround in the meantime.
Well actually my previous comment was that I actually had already fixed it, and I was pointing out my observation on what caused it.
DeleteI did end up using the "re-save" method to make it add the CR token in there.
That aside, thanks for the responses!
Awesome, glad it's working for you. I'll update the code at some point; maybe this weekend...
DeleteHOW TO USE IT?
ReplyDeleteLoad the module then check the help for a bunch of examples:
DeleteGet-Help Edit-DTWCleanScript -Full
Import-Module .\DTW.PS.PrettyPrinterV1.psd1
DeleteGet-Help Edit-DTWCleanScript -Full
Here is another sample to test on:
ReplyDeletehttp://powershelldevtools.wordpress.com/2010/07/20/how-to-deal-with-a-combobox-control/
Before, it works
After, it kinda works but not quite and you get 4 errors.
--------
I was really looking forward to seeing this work.
I also hoped I could add it to PowerGUI as an add-on and then right-click on a TAB and select PrettyPrinter but no.
For V2 I hope you can add ways to select how much spacing it adds. I can't stand Double-spaced code because I need to scroll around too much to see a code block. I like tightly formatted code like this:
If ( ) {
[code]
} else {
[code]
}
yours does this (very AIR-y):
If ( )
{
[code]
}
else
{
[code]
}
The bug (which I haven't fixed; life has been busy) was handling files that used unix-style end of line characters (i.e. LF) instead of Windows-style EOL (CR+LF). Test files that failed had either all LF or a mix of each and this utility only currently handles only EOL CR+LF, so you might want to re-save your file explicitly with the Windows style and re-run the utility. I've seen this most often with script people cut-and-paste from browsers.
ReplyDeleteYeah, I totally agree with you on the spacing issue. One of the configurations I planned on having in V2 was the ability to tighten the code based on the overall expression length. So, if the expression length was below some value (you choose) and the tighten was enabled, it might even shorten a short code block like this:
if ($num -lt 5)
{
$num + 3
}
else
{
$num + 1
}
to this:
if ($num -lt 5)
{ $num + 3 }
else
{ $num + 1 }
or even this:
if ($num -lt 5) { $num + 3 } else { $num + 1 }
I normally like things spaced out but for concise code, especially if it's repeated often, all in one line is nice.
Also, I started down the road awhile back to PowerGUI-ify it but again, ran out of time, alas.
ok...forgive me for being a total Noob or not exactly knowing what to do with this..recently I was flamed by someone for not formatting my scripts properly..being new to PS and not having any formal script training I have a hard time with that..and no one seems to want to teach me..so..I found your tool..exactly what do I do with the files once I have downloaded them?
ReplyDeleteI created a new folder in the modules directory..copied the files into it..open PS..type IPMO edit-DTWCleanscript and get a message stating no valid module is found..
for us noobs can you provide a "readme" file in the zip file?
No problem, Fed Up. This isn't a built-in PowerShell module, it's a custom one, so the easiest way to load it is store it some custom folder and then load it into your shell using the full path. So let's say you downloaded and unzipped the files to c:\temp\PS, you would load my module into your shell using this:
ReplyDeleteImport-Module "C:\temp\PS\DTW.PS.PrettyPrinterV1.psd1"
You should now be able to see it in memory by typing Get-Module; it'll list at least mine (if not more) with the Name DTW.PS.PrettyPrinterV1. You will also see two ExportedCommands, one of which is Edit-DTWCleanScript. That's the main function that you call to clean a script.
To see a few examples of how to use Edit-DTWCleanScript plus the rest of it's help, call the function with Get-Help:
Get-Help Edit-DTWCleanScript -Full
Please read all the help text - the SINGLE most important detail in there is MAKE SURE YOU BACK UP YOUR SCRIPT before running this utility on it. My function, by default, is designed to rewrite your files in place, overwriting the original copies. You need to check in your file, copy it to a temp location or test on a temp copy first - and then maybe diff after - to ensure that it doesn't screw up your script.
Now, all that said, this is V1 of the PrettyPrinter (and I don't know if I'll ever get to V2). This version does not restructure your code across lines - it does not move script block openings, closings, etc. (that was planned for V2), it cleans up whitespace, expands types names, resolves aliases and a bunch of other things.
If you are looking for something to completely rewrite your code and make it "perfect", this may not be the exact tool, but it's a good start. If you want a good example to learn from - to see how to write detailed, clean code with proper help, types on parameters, etc. - take a look at other people's code. Start with mine - download PowerGUI (a PowerShell editor) and take a look through the file DTW.PS.PrettyPrinterV1.psm1. That's the best way to learn, but mind you, everyone formats their code a little differently depending their background.
Have fun learning PowerShell!
When do you plan to release the prospected V2 :)
ReplyDeleteOh, no idea. I started a new job last year and I've been crazy busy since. I have no idea when I'll get back to this... :-(
ReplyDeleteWell done. This is the only thing out there of its kind that I see. Version 2? Hell, if you charged a little bit...
ReplyDeleteThanks! I haven't done any serious PowerShell scripting in some time so I'm thinking about start v2 soon. Please comment if you have any functionality requests.
ReplyDeleteWould be cool to configure spacing for () and {}.
ReplyDeleteI like to use tabs to indent.
If you put the code up on github and then I would be happy to help contribute.
The script is removing brackets
ReplyDeleteFor example [System.Windows.Forms.MessageBox] would end up with brackets removed, thus not working. Everything else seems to be intact.
Very useful. Thank you.
ReplyDeleteHello,
ReplyDeleteI know this is a old post, but i was getting the below error when i run your script....
PS U:\> Edit-DTWCleanScript -SourcePath C:\users\user\Downloads\test.ps1
Reading source: C:\users\user\Downloads\test.ps1
Tokenizing script content
Tokenize-SourceScriptContent : Tokenize-SourceScriptContent:: error occurred tokenizing source content
At C:\Users\user\Downloads\PS\DTW.PS.PrettyPrinterV1.psm1:1513 char:5
+ Tokenize-SourceScriptContent -EV Err
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Tokenize-SourceScriptContent
Property 'Name' cannot be found on this object. Make sure that it exists.
At C:\Users\user\Downloads\PS\DTW.PS.PrettyPrinterV1.psm1:766 char:33
+ Write-Error -Message "$($MyInvocation.MyCommand.Name):: $($_.Message) Co ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], PropertyNotFoundException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
:: Unrecognized token in source text. Content: @, line: 259, column: 79
At C:\Users\user\Downloads\PS\DTW.PS.PrettyPrinterV1.psm1:765 char:29
+ $Err | ForEach-Object {
+ ~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
Property 'Name' cannot be found on this object. Make sure that it exists.
At C:\Users\user\Downloads\PS\DTW.PS.PrettyPrinterV1.psm1:766 char:33
+ Write-Error -Message "$($MyInvocation.MyCommand.Name):: $($_.Message) Co ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], PropertyNotFoundException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
This comment has been removed by the author.
ReplyDeleteNice tool dude! I always struggle with making my scripts look neat.
ReplyDeleteYour script doesn't support full UNC paths. I.e., this doesn't work:
ReplyDeletePS C:\> Edit-DTWCleanScript \\my_share_server\share_drive\start-Procmon.ps1
Exception calling "ReadAllText" with "1" argument(s): "The given path's format is not supported."
At C:\Users\username\Documents\WindowsPowerShell\Modules\DTW.PS.PrettyPrinter\DTW.PS.PrettyPrinterV1.psm1:735 char:5
+ $script:SourceScriptString = [System.IO.File]::ReadAllText($SourcePath)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NotSupportedException
Tokenizing script content
Edit-DTWCleanScript : Edit-DTWCleanScript :: error occurred during processing
At line:1 char:1
+ Edit-DTWCleanScript -SourcePath \\my_share_server\share_drive\start-Procmon.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Edit-DTWCleanScript
Edit-DTWCleanScript : Exception calling ".ctor" with "3" argument(s): "The given path's format is not supported."
At line:1 char:1
+ Edit-DTWCleanScript -SourcePath \\my_share_server\share_drive\start-Procmon.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Edit-DTWCleanScript
Write destination file: Microsoft.PowerShell.Core\FileSystem::\\my_share_server\share_drive\start-Procmon.ps1
Finished in 0.285 seconds.
Although, that looks like it's a bug with System.IO.File rather than in your code. Just figured I'd point it out. Simple work around is to just copy the file to a place with a drive letter, and then copy it back.
Just what the doctor ordered - thank you so much!
ReplyDeleteDan, is there any chance you might have interest in being involved in making a beautifier for Atom? https://github.com/Glavin001/atom-beautify/issues/333
ReplyDeleteI believe you're existing module could do the trick, and there seems to be a number of interested people on github.
Hey Eric -
ReplyDeleteI've been planning on putting the putting the PowerShell beautifier / pretty printer up on GitHub in the next few weeks. As part of that I'd love to work with the atom-beautify folks to get PowerShell support integrated.
-Dan
Imagine my surprise when opening this page! Keep up the good stuff and Hello from Luxembourg!
ReplyDeleteYou are my hero! Thanks for sharing it on github.
ReplyDeleteI would recommend my profile is important to me, I invite you to discuss this topic... Buy Espon printer
ReplyDeleteHi Dan,
ReplyDeleteI am currently developing also an PowerShell Code beautifier.
Some idias are taken from your Module.
THANK YOU for your Inspiration!
If you want have a look into, it is here.
https://github.com/Kriegel/BeautyOfPower
Hey Peter -
DeleteNice work - I'm going to check it out! I had thought about rewriting the Beautifier to use AST a few years ago but ran into some issues - including not having any time. It's amazing how fun writing PS utilities is; I wish I could do it as a full-time job.
-Dan
This continues to be a very useful tool. Surprised there's no Prettier plugin by now, but this is a wonderful solution. 👍
ReplyDeleteThank you for this! Whenever you write a tool you never if anyone actually uses or cares about it; these occasional bits of feedback are great.
Delete