Sonntag, 31. Januar 2010

Out-ISEFile (V 0.9)

Just read the following tweet:

@MaxTrinidad Need the path to the various profile scripts on a system? Try $profile.psextended | Format-List (Try it in #PowerShell ISE too)

from @alexandair.

As I'm using ISE all day long, I tried it immedeatly and here is my result:
AllUsersAllHosts : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShellISE_pro
file.ps1
CurrentUserAllHosts : C:\Users\berndk.MMEDVNT\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\berndk.MMEDVNT\Documents\WindowsPowerShell\Microsoft.PowerShe
llISE_profile.ps1



Oops again the PowerShells f***ing wrapping behavior. It's time to write a work around:

function Out-IseFile            
{

[CmdletBinding()]
param (
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)]
$msg,
[Parameter(Position = 1, Mandatory = $False, ValueFromPipeline = $False)]
$path = $null,
[switch]$fl
)
if ($fl)
{
$msg = $msg | Format-list | out-string -width 2000
}

$count = $psise.CurrentPowerShellTab.Files.count
$null = $psIse.CurrentPowerShellTab.Files.Add()
$Newfile = $psIse.CurrentPowerShellTab.Files[$count]
if ($path)
{
$NewFile.SaveAs($path)
$NewFile.Save([Text.Encoding]::default)
}
$Editor = $Newfile.Editor
#$Editor.SetCaretPosition($Editor.LineCount, 1)

$ar = $msg -split "`r`n" | %{ $_ -replace '\s*$', '' }

$msg = $ar -join "`r`n"

$msg = $msg + "`r`n"
$Editor.InsertText($msg)
$Newfile

}


$null = $profile.psextended | out-IseFile -fl



To use it just type:
$null = $profile.psextended | out-IseFile -fl


Please note: this is work in progress.
The main trick is to use Out-String with a long width parameter, that suppresses the unwanted wrapping.
For some reason (I guess it's just what we call in German Gedankenlosigkeit) Out-String emits trailing blanks. I included code to get rid of it.

Because Format-List seams rather frequent, I added the fl switch.

Remeber when ISE-Output pane behaves naughty ( and that is quite often) just send your output to a fresh editor. That makes it easy to edit and save it, if you want.

Have fun extending ISE.

Bernd

PS: Thanks to JS who made it extendable.

Sonntag, 24. Januar 2010

Remove-DuplicateFile the ISE Way

The last days I spent checking some PowerShell scripts for finding and deleting duplicate files.

First we need to define which files are equal.
For most big files the criteria is simple: they are equal, when they are true copies of each other.
The second group contains text files you have edited. Here you often want to ignore some insignificant differences like trailing blanks etc. Take a look at the options of the old fc command

Let us focus today on the first approach. The best solution I found on the web is from Jason Stangroome. From him I copied the idea to use the Get-MD5 function, which closes the streams.

Without it you have some fun googling for 'The Process cannot access the file because a it is being used by another process'. Yes there seems to be ways to close files even in PowerShell, but PowerShell documentation is somewhat biased.

Using those Get-MD5 function, there is no problem in invoking Remove-item on the File you found, if you are decided to delete it.

I would prefer to use the delete method of the [System.IO.FileInfo], but I get 'Exception calling "Delete" with "0" argument(s): "Access to the path 'xxx.ps1' is denied.'
On the other side
Remove-item $($file.Fullname) -force

works fine.

I'm not following Jason in sorting the files acording to there length, but keep to a simpler design of comparing the files in the order they are output by Get-Childitem.

Instead of using -whatif I chose a different way. I just output the commands and some comments to a new ISE-file.

There I can check, whether I want to perform the delete or not and at the same time I have a fine log of the files I deleted.

The following code assumes you use PowerShell V2' ISE:

function Get-MD5([System.IO.FileInfo] $file = $(throw 'Usage: Get-MD5 [System.IO.FileInfo]'))            
{
# This Get-MD5 function sourced from:
# http://blogs.msdn.com/powershell/archive/2006/04/25/583225.aspx
$stream = $null;
$cryptoServiceProvider = [System.Security.Cryptography.MD5CryptoServiceProvider];
$hashAlgorithm = new-object $cryptoServiceProvider
$stream = $file.OpenRead();
$hashByteArray = $hashAlgorithm.ComputeHash($stream);
$stream.Close();

## We have to be sure that we close the file stream if any exceptions are thrown.
trap
{
if ($stream -ne $null) { $stream.Close(); }
break;
}

return [string]$hashByteArray;
}

function New-IseFile ($path)
{
$count = $psise.CurrentPowerShellTab.Files.count
$null = $psIse.CurrentPowerShellTab.Files.Add()
$Newfile = $psIse.CurrentPowerShellTab.Files[$count]
$NewFile.SaveAs($path)
$NewFile.Save([Text.Encoding]::default)
$Newfile

}

function Write-IseFile($file, $msg)
{
$Editor = $file.Editor
$Editor.SetCaretPosition($Editor.LineCount, 1)
$Editor.InsertText(($msg + "`r`n"))
}


function Remove-DuplicateFile
{
[CmdletBinding()]
param (
$path = ”.\”,
$extension = ”*.*”,
[switch]$delete
)


$Newfile = New-IseFile "$(get-location)\Delete-DuplicateFiles_$(get-date -f "yyyy-MM-dd-HH").ps1"

$hashtable = new-object system.collections.hashtable

# $global:filesToDelete = @()
$global:totalLength = 0

get-childitem -path $path $extension -recurse | where-object { ! $_.PSIsContainer } |
% {
$file = $_
$hashvalue = Get-MD5 $file
$length = $_.Length
trap {
$global:totalLength += $length
$msg = @"

# current {0} Byte total: {1,8:f3} MB"
# Remove-Item `'$($hashtable[$hashvalue])`' -force
Remove-Item `'$($file.Fullname)`' -force
"@
-f $length, $($global:totalLength/ 1MB)
$msg
Write-IseFile $Newfile $msg
# $global:filesToDelete += $file.Fullname
if ($delete)
{
Remove-item $($file.Fullname) -force
#$file.delete()

}
continue
}
$hashTable.Add($hashvalue, $file.FullName)
}

# Write-Host "`r`nFiles to delete`r`n"

# foreach ($f in $filesToDelete) {
# Write-Host "Remove-Item `'$f`' -force"
# }
$NewFile.Save()
}



Now you I call it with something like

Remove-DuplicateFile D:\myFiles,D:\mybackup

and get the new new ISE-Editor.

Attention using the -delete parameter the deletes where executed immediately.

Sonntag, 17. Januar 2010

My Diskqueue Problem

I'm running Windows 7 and every now and than my system is nonresponsive for 60 seconds.
The esource monitor shows something like the following. Most of my current PowerShell scripts are attemps to trace down this problem.



Any ideas ?

Eval-Min, Eval-Max, $null Part II

# The following relations are valid            
# All PowerShellStatements eval to True

# -2 < -1 < NULL < 0 < 1 < 2

-2 -lt -1
-1 -lt $null
$null -lt 0
0 -lt 1
1 -lt 2

$null -lt 1

# 2 > 1 > 0 > NULL > -1 > -2

2 -gt 1
1 -gt 0
0 -gt $null
$null -gt -1
-1 -gt -2

1 -gt $null


# 0 is not NULL

0 -ne $null
$null -ne 0

function Eval-Max
{
$max = $null
foreach ($i in $args){
if ($i -ne $null){
if( $i -gt $max -or $max -eq $null){
$max = $i
}
}
}
$max
}

function Eval-Min
{
$min = $null
foreach ($i in $args){
if ($i -ne $null){
if( $i -lt $min -or $min -eq $null){
$min = $i
}
}
}
$min
}

# $null handeled korrect

Eval-Min $null -1
Eval-Max $null -1
Eval-Min -1 $null
Eval-Max -1 $null


# The following seem wrong

Eval-Min -2 -1
Eval-Max -2 -1


To understand what happens look at

function Show-Type (){             
($args[0]).gettype()
}
Show-type 1
Show-type -1

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True String System.Object



I find this different behaviour is rather confusing. May we call it a bug?

When you cast the arguments to integers, you get the expected result.

Eval-Min $([int]-2) $([int]-1)            

-2


When you are working with properly typed input, the functions work as aspected.

Samstag, 16. Januar 2010

Eval-Min, Eval-Max, $null

When you want to determine the minimum or maximum of some property of all items in a pipeline, you can use Measure-Object.

But when you want to do some kind of performance monitoring, you want to eval the min or max so far, without stoping the running pipeline.

Primary to improve readability in my script I wrote the following two functions.

Attention I just found wrong results for negative arguments. I'm still checking. Look at the follow-ups.

function Eval-Max             
{
$max = $null
foreach ($i in $args){
if ($i -gt $max){
$max = $i
}
}
$max
}


function Eval-Min
{
$min = $null
foreach ($i in $args){
if (($i -ne $null -and $i -lt $min) -or $min -eq $null){
$min = $i
}
}
$min
}


Note that the two functions are not symmetric. Unlike SQL here in PowerShell we have

$null -lt 1            
True

$null -gt 1
False


PowerShell $null seems to have funny mathematical behaviour. It is less than 0 but greater than each negative number. In any case it is different from SQL NULL. be warned. Me functions here follow the SQL pattern. Therefore

Eval-Min $null 1            
1


Please if you know of earlier similar PowerShell functions drop a comment.

Montag, 4. Januar 2010

Use PowerShell to get openfiles with handle.exe

You find handle.exe at http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx.

You can use the following script to store a snapshot of the open files in a grid. You have to adapt the path to handle.exe.

C:\Usr\sysint\handle.exe | foreach{            
if ($_ -match '^(?<program>\S*)\s*pid: (?<pid>\d*)\s*(?<user>.*)$') {
$matches | %{
$id = $_.pid
$program = $_.program
$user = $_.user
}
}
if ($_ -match '^\s*(?<handle>[\da-z]*): File \((?<attr>...)\)\s*(?<file>(\\\\)|([a-z]:).*)') {
#$_
$matches | select @{n="Pid";e={$id}}, @{n="Program";e={$program}}, @{n="User";e={$user}}, @{n="Handle";e={$_.handle}}, @{n="attr";e={$_.attr}}, @{n="Path";e={$_.file}}
}

} | out-Gridview



I would like to fetch just the files in the current disk queue.

2010-01-16 Edited. I had a problem with html escape characters. Thanks for the comment.

Montag, 28. Dezember 2009

Compare-Object & get-wmiobject win32_process does it right

Hello
the fasted way to have lots of processes with the same ProcessName is use Chrome, which opens for each tab its own process and uses a few of it's own. I heard rumours that firefox will use seperate processes in future versions too. But here I'm not going to start any process vs. thread discussion. Here I will play with processes with the same name. You can just start Notepad for this purpose.

get-process | ? {$_.Name -eq 'Notepad' } | Kill            


Notepad
Notepad
notepad
notepad

$gp_a = get-process notepad
$gwmip_a = get-wmiobject win32_process | ? {$_.Name -eq 'notepad.exe'}


The code above just ensures, that there are 4 instances of notepad running.
Next close one of the first 3 instances and take fresh snapshots.

$gp_b = get-process notepad             
$gwmip_b = get-wmiobject win32_process | ? {$_.Name -eq 'notepad.exe'}


Let's look at them:

$gp_a            


Initially there where 4 processes:


PS C:\Var\bin> $gp_a

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
69 3 992 7612 72 1492 notepad
69 3 976 4220 55 2272 notepad
68 3 972 4096 55 3368 notepad
13 1 284 60 2 5480 notepad



$gp_b


In second snapshot there are 3 processes (PID 2272 got killed)


PS C:\Var\bin> $gp_b

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
72 3 1012 7852 59 1492 notepad
72 3 1012 7800 59 3368 notepad
72 3 1020 7960 59 5480 notepad



Compare-Object $gp_a $gp_b |             
Where-object {$_.SideIndicator -ne '=='} |
Select -ExpandProperty InputObject


Trying to use Compare-Object on the results of get-Process, as recommended in http://technet.microsoft.com/en-us/library/ee156812.aspx I get a wrong result


PS C:\Var\bin> Compare-Object $gp_a $gp_b |
Where-object {$_.SideIndicator -ne '=='} |
Select -ExpandProperty InputObject


Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
13 1 284 60 2 5480 notepad



(Compare-Object $gwmip_a $gwmip_b |             
Where-object {$_.SideIndicator -ne '=='}).Inputobject |
Select ProcessId, Name


while good old WMI gives the correct answer


PS C:\Var\bin> (Compare-Object $gwmip_a $gwmip_b |
Where-object {$_.SideIndicator -ne '=='}).Inputobject |
Select ProcessId, Name


ProcessId Name
--------- ----
2272 notepad.exe




The documentation of Compare-Object contains the funny sentense:
The result of the comparison indicates whether a property value appeared only in the object from the Reference set (indic
ated by the <= symbol), only in the object from the Difference set (indicated by the => symbol) or, if the IncludeEqual p
arameter is specified, in both objects (indicated by the == symbol).


Does it chose a property by incident and in the case of Get-Process, does it use ProcessName instead of Id ?


Is there someone who can tell more about the logic Compare-Object uses to determine whether two objects are the same.