Powershell script that extracts medadata from powershell script - powershell-2.0

Does anybody know of a script or powershell windows utility, or 3rd party utility, codeplex, poshcode script, app, whatever etc, which can read and scan a powershell script and detail what snapins, providers, assemblies, cmdlets, functions, etc, etc, etc, the script needs to execute.
Thanks.
Bob.

First things first: No, I don't know of such a script/utility.
I suspect, though, that you can get pretty far with Powershell's capabilities of parsing itself.
For example, the following script:
function test {}
test
$content = gc $args[0]
[System.Management.Automation.PsParser]::Tokenize($content, [ref] $null) |
? { $_.Type -eq "Command" } | ft
given itself as argument yields the following output:
Content Type Start Length StartLine StartColumn EndLine EndColumn
------- ---- ----- ------ --------- ----------- ------- ---------
test Command 20 4 3 1 3 5
gc Command 39 2 5 12 5 14
? Command 127 1 6 76 6 77
ft Command 157 2 6 106 6 108
So, the "Command" type includes at least functions and cmdlets. You can further dissect this by un-aliasing those tokens.
This probably can tell you a little already but comes nowhere close to your pretty exhaustive list of what Powershell scripts could require.
But at least in the case of snap-ins or modules you probably need some magic anyway to know precisely what's missing.

Actually, we make several of these utilities:
http://scriptcop.start-automating.com - Free online / downloadable static analysis of PowerShell scripts
http://scriptcoverage.startautomating.com/ - Code Coverage Tool for Powershell Scripts
There's also a tool in the module ShowUI (Get-ReferencedCommand) that does exactly what you'd want. It looks at every command used within a command, and every command they use, to produce a list of all commands you must include.
You use it like: Get-ReferenedCommand -ScriptBlock { Start-MyCommand }
Hope this Helps

Related

How to print the starting position of pattern in grep

In python's regex (re) library I can do re.search("<pattern>", string).start() to get the start of the pattern (if pattern exists).
How can I do the same in the unix command line tool grep?
E.g. If pattern= "th.n" and the string is "somethingwrong", I expect to see the number 5 (considering 1-based but 4 in a 0-based would be ok)
Thank you!
For example:
echo "abcdefghij" | grep -aob "e"
outputs :
4:e
Here:
-b to gets the byte offset
-a tells grep to use the input as text
-o outputs the findings
With your example:
echo ""somethingwrong"" | grep -aob "th.n"
4:thin
This works great on multiple matches:
echo "abcdefghiqsdqdqdfjjklqsdljkhqsdlf" | grep -aob "f"
5:f
16:f
32:f
Maybe a Perl one-liner would be a happy medium between having to write a Python program and the simplicity of a standard Unix tool.
Given this file:
$ cat foo.txt
This thing
that thing
Not here
another thing way over here that has another thing and a third thing
thank you.
You could run this Perl one-liner:
$ perl -lne'while(/th.n/g){print $.," ",$-[0]," ",$_;}' foo.txt
1 5 This thing
2 5 that thing
4 8 another thing way over here that has another thing and a third thing
4 45 another thing way over here that has another thing and a third thing
4 63 another thing way over here that has another thing and a third thing
5 0 thank you.
Also, the greplike search tool ack (that I wrote)has a --column option to display the column:
$ ack th.n --column foo.txt /dev/null
foo.txt
1:6:This thing
2:6:that thing
4:9:another thing way over here that has another thing and a third thing
5:1:thank you.
Or with the --nogroup option so the filename appears on each line.
$ ack th.n --column --nogroup foo.txt /dev/null
foo.txt:1:6:This thing
foo.txt:2:6:that thing
foo.txt:4:9:another thing way over here that has another thing and a third thing
foo.txt:5:1:thank you.
I had to add the search of /dev/null because ack's output would be different if there was only one file being searched.
ripgrep has a --column option, too.
$ rg --column --line-number th.n foo.txt
1:6:This thing
2:6:that thing
4:9:another thing way over here that has another thing and a third thing
5:1:thank you.

What tools deal with spaces in columnar data well?

Let's start with an example that I ran into recently:
C:\>net user
User accounts for \\SOMESYSTEM
-------------------------------------------------------------------------------
ASPNET user1 AnotherUser123
Guest IUSR_SOMESYSTEM IWAM_SOMESYSTEM
SUPPORT_12345678 test userrrrrrrrrrrr test_userrrrrrrrrrrr
The command completed successfully.
In the third row, second column there is a login with a space. This causes many of the tools that separate fields based on white space to treat this field as two fields.
How would you deal with data formatted this way using today's tools?
Here is an example in pure** Windows batch language on the command prompt that I would like to have replicated in other modern cross-platform text processing tool sets:
C:\>cmd /v:on
Microsoft Windows [Version 5.2.3790]
(C) Copyright 1985-2003 Microsoft Corp.
C:\>echo off
for /f "skip=4 tokens=*" %g in ('net user ^| findstr /v /c:"The command completed successfully."') do (
More? set record=%g
More? echo !record:~0,20!
More? echo !record:~25,20!
More? echo !record:~50,20!
More? )
ASPNET
user1
AnotherUser123
Guest
IUSR_SOMESYSTEM
IWAM_SOMESYSTEM
SUPPORT_12345678
test userrrrrrrrrrrr
test_userrrrrrrrrrrr
echo on
C:\>
** Using variable delayed expansion (cmd /v:on or setlocal enabledelayedexpansion in a batch file), the for /f command output parser, and variable substring syntax... none of which are well documented except for at the wonderful website http://ss64.com/nt/syntax.html
Looking into AWK, I didn't see a way to deal with the 'test userrrrrrrrrrrr' login field without using substr() in a similar method to the variable substring syntax above. Is there another language that makes text wrangling easy and is not write-only like sed?
PowerShell:
Native user list example, no text matching needed
Get-WmiObject Win32_UserAccount | Format-Table -Property Caption -HideTableHeaders
Or, if you want to use "NET USER":
$out = net user # Send stdout to $out
$out = $out[4..($out.Length-3)] # Skip header/tail
[regex]::split($out, "\s{2}") | where { $_.Length -ne 0 }
# Split on double-space and skip empty lines
Just do a direct query for user accounts, using vbscript (or powershell if your system supports)
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_UserAccount",,48)
For Each objItem in colItems
Wscript.Echo objItem.Name
Next
This will show you a list of users, one per line. If your objective is just to show user names, there is no need to use other tools to process thee data.
Awk isn't so great for that problem because awk is focused on lines as records with a recognizable field separator, while the example file uses fixed-width fields. You could, e.g., try to use a regular expression for the field separator, but that can go wrong. The right way would be to use that fixed width to clean the file up into something easier to work with; awk can do this, but it is inelegant.
Essentially, the example is difficult because it doesn't follow any clear rules. The best approach is a quite general one: write data to files in a well-defined format with a library function, read files by using a complementary library function. Specific language doesn't matter so much with this strategy. Not that that helps when you already have a file like the example.
TEST
printf "
User accounts for \\SOMESYSTEM
-------------------------------------------------------------------------------
ASPNET user1 AnotherUser123
Guest IUSR_SOMESYSTEM IWAM_SOMESYSTEM
SUPPORT_12345678 test userrrrrrrrrrrr test_userrrrrrrrrrrr
The command completed successfully.
\n" | awk 'BEGIN{
colWidth=25
}
/-----/ {next}
/^[[:space:]]*$/{next}
/^User accounts/{next}
/^The command completed/{next}
{
col1=substr($0,1,colWidth)
col2=substr($0,1+colWidth,colWidth)
col3=substr($0,1+(colWidth*2),colWidth)
printf("%s\n%s\n%s\n", col1, col2, col3)
}'
There's probably a better way than the 1+(colWidth*2) but I'm out of time for right now.
If you try to execute code as is, you'll have to remove the leading spaces at the front of each line in the printf statement.
I hope this helps.
For this part:
set record=%g
More? echo !record:~0,20!
More? echo !record:~25,20!
More? echo !record:~50,20!
I would use:
for /f "tokens=1-26 delims= " %a in (%g%) do (
if not "%a" = "" echo %a
if not "%b" = "" echo %b
if not "%c" = "" echo %c
rem ... and so on...
if not "%y" = "" echo %y
if not "%z" = "" echo %z
)
That is if I had to do this using batch. But I wouldn't dare to call this "modern" as per your question.
perl is really the best choice for your case, and millions of others. It is very common and the web is ripe with examples and documentation. Yes it is cross platform, extremely stable, and nearly perfectly consistent across platforms. I say nearly because nothing is perfect and I doubt in your lifetime that you would encounter an inconsistency.
It is a language interpreter but supports a rich command-line interface as well.

Oracle SqlPlus Command Line: is there a way to concatenate set options?

I need to set up some SET options in Oracle SQLplus command line program each time I use it, such as SET HEADING OFF and the likes to beautify my results.
I found that I always have to input each line separately so Set different options and this is becoming annoying since I need to access it many times a day.
I found that there's no way to separate different SET commands with semicolons because it doesn't accept it:
SET HEADING OFF; SET LINESIZE 100;
returns an error
A solution could be adding them to a control script and create a shell alias, but I know control scripts execute and then exit and don't return you control over the command line.
So, anybody knows another solution? Or am I missing something?
Ok, answering my own question: apprently it is possible to do this:
SET HEADING OFF LINESIZE 100 PAGESIZE 0 xxx xxx
And go on adding rules as one comes up with them.
It is a simple and effective solution for now.
Put all your commands in a ".sql" file (for example "format.sql") then execute them with the "#" command in Sql*plus (for example "#format").
Note that it defaults to the ".sql" suffix when looking for the command file.
For example, if "format.sql" contains the commands "set linesize 100" and "set pagesize 0":
% sqlplus
SQL*Plus: Release 10.2.0.1.0 - Production on Thu Mar 18 08:39:03 2010
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bit Production
SQL> show linesize
linesize 80
SQL> #format
SQL> show linesize
linesize 100
SQL> select 1+1 from dual;
2

tf diff: why doesn't the command line diff recognize a valid version of a file sometimes?

I'm using the TFS Power Toys with PowerShell to get the history of a file. Like so:
$fileName = "$/MyDir/MyFile.cs"
$results = #(Get-TfsItemHistory $fileName )
I get a nice result set that has many ChangesetId's. However, when I run tf diff (tf diff /version:C36826~C36680 "$/MyDir/MyFile.cs" /format:unified) for some of the ChangesetIds I get:
Item $/MyDir/MyFile.cs;C37400 was not found in source control.
However I can use the compare tool from Visual Studio to compare those two versions of the file. Am I doing something wrong? It doesn't seem to have anything to do with the age of the file, there's instances where the command line diff will show a changeset but not a changeset that happened earlier in the day. When I view those changesets with the gui tool they have many lines that have changed, the changeset isn't empty.
What's up with this thing? Should I submit a bug report? This looks like a bug to me.
Maybe this has something to do with it: the last diff that works gives me "\ No newline at end of file".
I'll bet the file has been renamed. Luckily you are already using Powershell, so this is fairly straightforward to track down:
tfhistory "$/MyDir/MyFile.cs" -all | select changesetid, #{name="Path"; expression={$_.changes[0].item.serveritem}} | ft -auto
You'll then need to run diff using a slightly more verbose syntax:
tf diff "$/MyOtherDir/MyFile.old.cs;1234" "$/MyDir/MyFile.cs;5678"
[EDIT] The first command should print something like:
C:\workspaces\temp> tfhist rentest2 -all | select changesetid, #{name="Path"; expression={$_.changes[0].item.serveritem}} | ft -auto
ChangesetId Path
----------- ----
10725 $/Test-ConchangoV2/rentest2
10142 $/Test-ConchangoV2/rentest
As you can see, I personally have Get-TfsItemHistory aliased to 'tfhist' for even shorter typing. 'tfhistory' is what the PS console in the Power Tools uses, so that's what I put in my original instructions.

get a set of files that have been modified after a certain date

Does anyone have a handy powershell script that gets a set of files from TFS based on a modification date? I'd like to say "give me all the files in this folder (or subfolder) that were modified after X/Y/ZZZZ" and dump those files to a folder other than the folder they would normally go to. I know enough powershell to hack about and get this done, eventually, but I'm hoping to avoid that.
Make sure you have the Team Foundation 2015 Power Tools installed. It comes with a PowerShell snapin. You can run the PowerShell console file right from its startup group or you can execute Add-PSSnapin Microsoft.TeamFoundation.PowerShell. Then cd to your workspace and execute:
Get-TfsItemProperty . -r | Where {$_.CheckinDate -gt (Get-Date).AddDays(-30)} |
Format-Table CheckinDate,TargetServerItem -auto
CheckinDate TargetServerItem
----------- ----------------
9/14/2009 1:29:23 PM $/Foo/Trunk/Bar.sln
9/29/2009 5:08:26 PM $/Foo/Trunk/Baz.sln
To dump that info to a dir:
Get-TfsItemProperty . -r | Where {$_.CheckinDate -gt (Get-Date).AddDays(-30)} |
Select TargetServerItem > c:\recentlyChangedFiles.txt
To copy those files to another dir (this assumes you have them pulled down locally into a workfolder):
Get-TfsItemProperty . -r | Where {$_.CheckinDate -gt (Get-Date).AddDays(-30)} |
CopyItem -Path $_.LocalItem -Destination C:\SomeDir -Whatif
Note this copies files into a flat folder structure. If you want to maintain the dir structure it is a bit more involved.
Using Get-TfsItemProperty like Keith doesn't just require a workspace for the file copies. It's the wrapper for GetExtendedItems(), the server query for local info most commonly seen in Source Control Explorer. By relying on the version info it reports, you assume the files themselves were downloaded (more generally: synchronized, in the case of renames & deletes) in the last 30 days. If the workspace is not up to date, you'll miss some files / give them out-of-date names / etc. It's also quite expensive as informational commands go.
Some alternative examples:
# 1
Get-TfsChildItem $/FilesYouWant -R |
? { $_.CheckinDate -gt (Get-Date).AddDays(-30) } |
% { $_.DownloadFile(join-path C:\SomeDir (split-path $_.ServerItem -leaf)) }
# 2
Get-TfsItemHistory $/FilesYouWant -R -All -Version "D$((Get-Date).AddDays(-30).ToString('d'))~" |
Select-TfsItem |
Select -Unique -Expand Path |
Sort |
Out-File c:\RecentlyChanged.txt
The first is a straightforward adaptation of Keith's code, using a cheaper query and eliminating the workspace dependency. It's the best option if you know a high % of the items under that dir were modified recently.
The second option queries the changeset history directly. By letting the Where clause be computed in SQL instead of on the client, this can be an order of magnitude more efficient if a low % of the items were changed recently (as is often the case). However, it will lag the item-based queries if there are lots of large changesets returned, making the server's JOIN to grab the item properties expensive and forcing our client-side duplicate removal to do a lot of work.
[Yes, I know that having -Version require a string is not very Powershell-esque; mea culpa. You could create a DateVersionSpec with new-object and call its ToString(), but that's even more work.]
I didn't show every combination of API call + desired task. Goes without saying you can use #1 to generate the file list and #2 to (re)download by modifying the latter half of the pipeline. You can even combine that copy technique with the efficiency of Get-TfsItemHistory:
# 2b, with local-to-local copying
Get-TfsItemHistory $/FilesYouWant -R -All -Version "D$((Get-Date).AddDays(-30).ToString('d'))~" |
Select-TfsItem |
Select -Unique -Expand Path |
Get-TfsItemProperty |
Copy $_.LocalItem -Dest C:\SomeDir
It's true this makes a 2nd roundtrip to the server, but thanks to the initial query the GetExtendedItems() call will be scoped to the precise set of items we're interested in. And of course we remove any chance that download time becomes the bottleneck. This is likely the best solution of all when the # of changesets is small and the concerns I raised about Keith's workspace synchronization aren't relevant for whatever reason.
Can I just say that having to user powershell to do this seems absurd.
FWIW, I've been involved with TFS from inside & outside MS for 4.5yr and never seen this feature requested. If you could expand on what goal you're actually trying to accomplish, my guess is we could suggest a better way. Don't get me wrong, I wrote the Powershell extensions precisely to handle oddball scenarios like this. But frequently it's a job for another tool entirely, eg: Annotate, MSBuild, DB schema compare...

Resources