PowerShell Cookbook

Twitter Updates

    follow me on Twitter

    Search

    Categories

     

    On this page

    Delayed Screen Captures in PowerShell
    Where is Rename-Computer?
    An Exercise in De-Obfuscation
    Holiday Wishes
    Hex Dumper in PowerShell
    Testing for PowerShell Remoting: Test-PsRemoting
    Mathematical Pumpkins
    Removing Deleted Items Left by the iPhone
    Scripting Network / TCP Connections in PowerShell
    PowerShell Activity Tracker

    Archive

    Blogroll

    Disclaimer
    I work for Microsoft.

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    RSS 2.0 | Atom 1.0 | CDF

    Send mail to the author(s) E-mail

    Total Posts: 252
    This Year: 3
    This Month: 0
    This Week: 0
    Comments: 736

    Sign In

     Monday, January 18, 2010
    Monday, January 18, 2010 8:53:33 PM (Pacific Standard Time, UTC-08:00) ( )

    Here’s one that’s short and sweet. A lot of programs exist for the sole purpose of taking a screen shot after a certain amount of time. For example, delay for 10 seconds before taking a screen shot so that you have the time to open some menus.

    This is incredibly simple with PowerShell:

    Add-Type -Assembly System.Windows.Forms
    Sleep 10

    ## Capture the entire screen
    [System.Windows.Forms.Sendkeys]::SendWait("{PrtSc}")

    ## Capture the current window
    [System.Windows.Forms.Sendkeys]::SendWait("%{PrtSc}")

    Comments [1] | | # 
     Wednesday, January 13, 2010
    Wednesday, January 13, 2010 9:10:49 PM (Pacific Standard Time, UTC-08:00) ( )

    In PowerShell V2, we added a bunch of computer management cmdlets:

    PS >Get-Command -Noun Computer | Format-Table -Auto

    CommandType Name                Definition
    ----------- ----                ----------
    Cmdlet      Add-Computer        Add-Computer [-DomainName] <String> [-Credent
    Cmdlet      Checkpoint-Computer Checkpoint-Computer [-Description] <String> [
    Cmdlet      Remove-Computer     Remove-Computer [[-Credential] <PSCredential>
    Cmdlet      Restart-Computer    Restart-Computer [[-ComputerName] <String[]>]
    Cmdlet      Restore-Computer    Restore-Computer [-RestorePoint] <Int32> [-Ve
    Cmdlet      Stop-Computer       Stop-Computer [[-ComputerName] <String[]>] [[

    Astute followers of our CTP process might notice a missing entry in this list when compared to earlier builds: the Rename-Computer cmdlet.

    During testing and additional validation, we realized two things:

    • 1) The API used to rename computers is fairly “forgiving”
      If you specify an invalid computer name, the API often doesn’t reject it – but instead transforms it. For example, supplying a computer name that is too long will simply truncate the computer name and complete the operation. There’s nothing like losing a computer in your domain! Similarly, the API supports full Unicode input, while many applications break if the computer name is not the same as its NetBIOS name (which does not support full Unicode.)
    • 2) Our implementation failed to detect some higher-level scenarios that the existing NetDom.exe command detects
      NetDom.exe is a tool that already lets you rename computers, and users might expect some degree of compatibility with the safety it ensures. For example: in the machine a CA server? In the middle of a DC promo? In the middle of a role change?

    Since both netdom.exe and WMI (http://msdn.microsoft.com/en-us/library/aa393056(VS.85).aspx) are easily accessible alternatives, we decided to remove the cmdlet rather than ship something that didn’t meet expectations.

    Comments [0] | | # 
     Monday, January 04, 2010
    Monday, January 04, 2010 10:46:18 PM (Pacific Standard Time, UTC-08:00) ( )

    I don’t like to post line noise very often, but I did break that exception in the Holiday Wishes post a few weeks ago. I hate quoting entire blog posts, but, 177 characters isn’t too bad:

    '(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]::ToString($_, 2).Replace("0"," ")})-split''(.{12}|Mh)''|?{$_}'|%{iex $_;-join[char[]]$_[[char[]]"nOBB7[4oBCaenRa"]}

    When it runs, you get this for output:

    PS >'(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]::ToString
    ($_, 2).Replace("0"," ")})-split''(.{12}|Mh)''|?{$_}'|%{iex $_;-join[char[]
    ]$_[[char[]]"nOBB7[4oBCaenRa"]}                                            
         1                                                                     
        111                                                                    
       11111                                                                   
      1111111                                                                  
        111                                                                    
       11111                                                                   
     111111111                                                                 
    11111111111                                                                
        111                                                                    
        111                                                                    
    Merrv ChristMas                                                            

    While it may seem like magic, it in fact was not – just some terribly obtuse PowerShell scripting. Let’s take a look at what makes it tick. Rather than deconstruct it, we’ll build it up from scratch.

    In a text editor, I drew the tree you see in the main output. In thinking how to compress this down into a shorter script, the essence of the solution is that the tree is really just a set of ON (“1”) cells, and OFF (“ “) cells. Binary is a system that describes ON and OFF really well, so perhaps we can store the tree’s pattern of ON and OFF in a binary number?

    Converting this to more realistic binary, we get:

    000001000000
    000011100000
    000111110000
    001111111000
    000011100000
    000111110000
    011111111100
    111111111110
    000011100000
    000011100000

    Now, NUMBERS do a great job of encoding binary patterns. For example, “1234” is 10011010010 in binary. What if we just concatenate all those ones and zeros together to find the number it represents?

    PS >$result = [Convert]::ToInt64("00000100000000001110000000011111000000111
    111100000001110000000011111000001111111110011111111111000001110000000001110
    0000", 2)                                                                  
    Exception calling "ToInt64" with "2" argument(s): "Value was either too la 
    rge or too small for a UInt64."                                            
    At line:1 char:29                                                          
    + $result = [Convert]::ToInt64 <<<< ("000001000000000011100000000111110000 
    00111111100000001110000000011111000001111111110011111111111000001110000000 
    0011100000", 2)                                                            
        + CategoryInfo          : NotSpecified: (:) [], MethodInvocationExcep  
       tion                                                                    
        + FullyQualifiedErrorId : DotNetMethodException                        

    Unfortunately, there are too many bits (120) to fit in a single 64-bit number. While there are some “big number” libraries about, this should stand on its own. Instead, lets break it up into two smaller 60-bit chunks, and convert those:

    PS >[Convert]::ToInt64("000001000000000011100000000111110000001111111000000
    011100000", 2)                                                             
    18029799997931744                                                          
    PS >[Convert]::ToInt64("000111110000011111111100111111111110000011100000000
    011100000", 2)                                                             
    139752119745773792                                                         

    Perfect. There are the magic numbers you see at the beginning of the script. We should be able to recreate our greeting by converting them back to binary, and outputting them:

    PS >18029799997931744,139752119745773792 | Foreach-Object {                
    >>     [Convert]::ToString($_, 2)                                          
    >> }                                                                       
    >>                                                                         
    1000000000011100000000111110000001111111000000011100000                    
    111110000011111111100111111111110000011100000000011100000                  

    Hmm. These don’t match up any longer. Since leading zeroes are not important when converting numbers to binary, the .NET Framework doesn’t put any. After all, how many would it put? We can get around this problem by right-aligning the strings back to 60 characters:

    PS >18029799997931744,139752119745773792 | Foreach-Object {                
    >>     "{0,60}" -f [Convert]::ToString($_, 2)                              
    >> }                                                                       
    >>                                                                         
         1000000000011100000000111110000001111111000000011100000               
       111110000011111111100111111111110000011100000000011100000               

    That’s got the correct width again, but those zeroes are going to get in the way. Let’s convert them back to spaces:

    PS >18029799997931744,139752119745773792 | Foreach-Object {                
    >>     "{0,60}" -f [Convert]::ToString($_, 2).Replace("0"," ")             
    >> }                                                                       
    >>                                                                         
         1          111        11111      1111111       111                    
       11111     111111111  11111111111     111         111                    

    That’s getting pretty close. In fact, many trees look exactly like this when they come from a box. However, it’s not stacked properly – our original tree rows were 12 wide, and now they are 60. Busting out a gnarly word wrapping regular expression I blogged out a while ago, let’s apply it:

    PS >(                                                                      
    >>     18029799997931744,139752119745773792 | Foreach-Object {             
    >>         "{0,60}" -f [Convert]::ToString($_, 2).Replace("0"," ")         
    >>     }                                                                   
    >> ) -split '(.{12})' | Where-Object { $_ }                                
    >>                                                                         
         1                                                                     
        111                                                                    
       11111                                                                   
      1111111                                                                  
        111                                                                    
       11111                                                                   
     111111111                                                                 
    11111111111                                                                
        111                                                                    
        111                                                                    

    Hey, that rings a bell!

    The next step is to add the “Merry Christmas” greeting at the end. Rather than write it directly or invent some cool scheme to encode the characters, what if we picked characters from the script itself, so far? If we have access to the script as a string, we can use PowerShell’s array slicing to pick whatever letters we want out. For example:

    PS >"ABCDEF"[3,4,0,3,1,4,4,5]                                              
    D                                                                          
    E                                                                          
    A                                                                          
    D                                                                          
    B                                                                          
    E                                                                          
    E                                                                          
    F                                                                          

    We don’t actually have access to the script as a string, so instead – let’s pack it into a string first, and then run it with Invoke-Expression. We’ll drop a bunch of spaces and use a bunch of aliases to compress things further:

    '(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]::ToString($_, 2).Replace("0"," ")})-split''(.{12})''|?{$_}'|iex

    Now, how can we invoke that string, but also have it available for the letter picking process we still want to do? We could store it in a variable (and reuse that variable,) but it might be easier to use the Foreach-Object cmdlet to loop over the (single) string, and then reuse the implicit $_ variable.

    PS >'"Hello"' | Foreach-Object { Invoke-Expression $_; $_[3,2,2] }         
    Hello                                                                      
    l                                                                          
    e                                                                          
    e                                                                          

    PowerShell naturally emits those last objects one-by-one, so let’s join them into a string:

    PS >'"Hello"' | Foreach-Object { Invoke-Expression $_; -join $_[3,2,2] }   
    Hello                                                                      
    lee                                                                        

    That looks like the approach we need to pick the letters out of our original script. I generated a table and reviewed its output:

    PS >$script = '(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]
    ::ToString($_, 2).Replace("0"," ")})-split''(.{12})''|?{$_}'               
    PS >$x = 0; $script.ToCharArray() | % { "$x : $_"; $x++ }                  
    0 : (                                                                      
    1 : 1                                                                      
    2 : 8                                                                      
    3 : 0                                                                      
    4 : 2                                                                      
    5 : 9                                                                      
    6 : 7                                                                      
    51 : [                                                                     
    52 : C                                                                     
    53 : o                                                                     
    54 : n                                                                     
    55 : v                                                                     
    56 : e                                                                     
    57 : r                                                                     
    58 : t                                                                     
    59 : ]                                                                     
    116 : }                                                                    

    Unfortunately, the script output is missing a few chararcters: “M”, and “h”. It’s also missing a “y”, but using a “v” is a common trick due to its visual similarity. Where can we pack those letters in, though?

    The regular expression looks like the perfect place. It splits on sequences of 12 characters, so we could also ask it to split on the non-existent string “Mh”. It would have no impact, but would give us the characters we need. Some readers thought that the “v” instead of a “y” was a typo, so Robert (in the comments) fixed the script by adding the “y” to this throwaway piece of the regular expression.

    '(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]::ToString($_, 2).Replace("0"," ")})-split''(.{12}|Mhy)''|?{$_}

    After adding the extra text to the script, review the output again and get a big list of numbers:

    … –join $_[110,79,66,66,55,91,52,111,66,67,97,101,110,82,97]

    That’s unbecoming of an obfuscated script. Luckily, those numbers are in the regular ASCII range, and we can convert them to a string:

    PS >[char[]] (110,79,66,66,55,91,52,111,66,67,97,101,110,82,97)            
    n                                                                          
    O                                                                          
    B                                                                          
    B                                                                          
    7                                                                          
    [                                                                          
    4                                                                          
    o                                                                          
    B                                                                          
    C                                                                          
    a                                                                          
    e                                                                          
    n                                                                          
    R                                                                          
    a                                                                          
    PS >[int[]] [char[]] "nOBB7[4oBCaenRa"                                     
    110                                                                        
    79                                                                         
    66                                                                         
    66                                                                         
    55                                                                         
    91                                                                         
    52                                                                         
    111                                                                        
    66                                                                         
    67                                                                         
    97                                                                         
    101                                                                        
    110                                                                        
    82                                                                         
    97                                                                         

    PowerShell’s array slicing does automatic integer casting if needed, so we are fine just converting the string to a sequence of characters:

    … $_[[char[]]"nOBB7[4oBCaenRa"]}

    And there you have it: 1200 words to describe 177 characters.

    Comments [1] | | # 
     Friday, December 18, 2009
    Friday, December 18, 2009 10:17:11 PM (Pacific Standard Time, UTC-08:00) ( )

    '(18029799997931744,139752119745773792|%{"{0,60}"-f [Convert]::ToString($_, 2).Replace("0"," ")})-split''(.{12}|Mh)''|?{$_}'|%{iex $_;-join[char[]]$_[[char[]]"nOBB7[4oBCaenRa"]}

    Comments [4] | | # 
     Monday, November 23, 2009
    Monday, November 23, 2009 10:05:11 PM (Pacific Standard Time, UTC-08:00) ( )

    Marcel has been posting some interesting articles on using PowerShell to generate the MD5 hashes of files.  Now, an MD5 hash of a file is just an array of bytes.  Typical hashing programs display this in a more friendly manner:

    PS:15 C:\Temp >md5sum 71-59-B7.bmp
    a05805e638741bb767f97c0e88962952 *71-59-B7.bmp

    Although the output of Marcel’s scripts could definitely be crafted to display this output, they currently output the string representation of a byte array:

    PS:19 C:\Temp >get-md5 (get-childitem 71-59-B7.bmp)
    160 88 5 230 56 116 27 183 103 249 124 14 136 150 41 82

    One of the comments in response to Marcel’s post was that PowerShell should, by default, output byte arrays as hex.  This is a good suggestion, and we can go even further with it.  Let’s write a script to give us a full hex editor-like view of a byte array:

    PS:20 C:\Temp >get-md5 (get-childitem 71-59-B7.bmp) | format-hex


                0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F


    00000000   A0 58 05 E6 38 74 1B B7 67 F9 7C 0E 88 96 29 52   X.æ8t.•gù|.??)R

    Or even better, let’s use it to dump out a very small bitmap – 10 pixels of the colour (R=0x71 G=0x59 B=0xB7)

    PS:21 C:\Temp >Format-Hex 71-59-B7.bmp


                0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F


    00000000   42 4D 5E 00 00 00 00 00 00 00 36 00 00 00 28 00  BM^.......6...(.
    00000010   00 00 0A 00 00 00 01 00 00 00 01 00 20 00 00 00  ............ ...
    00000020   00 00 00 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00  ......Ä...Ä.....
    00000030   00 00 00 00 00 00 B7 59 71 FF B7 59 71 FF B7 59  ......•Yq.•Yq.•Y
    00000040   71 FF B7 59 71 FF B7 59 71 FF B7 59 71 FF B7 59  q.•Yq.•Yq.•Yq.•Y
    00000050   71 FF B7 59 71 FF B7 59 71 FF B7 59 71 FF        q.•Yq.•Yq.•Yq.

    To make it easier to determine byte offsets, files are usually broken down into 16-byte rows.  The left-hand section gives the offset of the 16-byte chunk.  The middle section gives the hex representation of the data at that location.  These pieces of data are aligned in columns also, corresponding to their location within the 16-byte chunk.  So column “E” in row 0x40 means a file offset of (0x40 + 0x0E) = 0x4E.  The last section gives an ASCII representation of the data.

    In this representation, it becomes possible to see some of the underlying structure of the bitmap format:

    Offset Length Comment
    0x00 2 “BM,” the magic bitmap header
    0x02 4 “0x5E,” the length of the file. Notice that our last data byte is at 0x5D.  Since we started counting from zero, this means that we have 0x5E bytes of data.
    (...) (...) (...)
    0x0A 4 “0x36”, specifies the absolute start of the bitmap data. Notice that the data begins at offset (0x30 + 0x06).
    0x36 40 10 4-byte pixel representations. In Bitmaps, they are laid out as (B=0xB7 G=0x59 R=0x71 <reserved>)

    Now, for the script:

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    ##############################################################################
    ##
    ## Format-Hex
    ##
    ## From Windows PowerShell Cookbook (O'Reilly)
    ## by Lee Holmes (http://www.leeholmes.com/guide)
    ##
    ##############################################################################

    <#

    .SYNOPSIS
    Outputs a file or pipelined input as a hexadecimal display. To determine the
    offset of a character in the input, add the number at the far-left of the row
    with the the number at the top of the column for that character.
     
    .EXAMPLE
    PS >"Hello World" | Format-Hex

                0 1 2 3 4 5 6 7 8 9 A B C D E F
     
    00000000 48 00 65 00 6C 00 6C 00 6F 00 20 00 57 00 6F 00 H.e.l.l.o. .W.o.
    00000010 72 00 6C 00 64 00 r.l.d.
     
    .EXAMPLE
    PS >Format-Hex c:\temp\example.bmp
      
    #>


    [CmdletBinding(DefaultParameterSetName = "ByPath")]
    param(
        ## The file to read the content from
        [Parameter(ParameterSetName = "ByPath", Position = 0)]
        [string] $Path,
       
        ## The input (bytes or strings) to format as hexadecimal
        [Parameter(
            ParameterSetName = "ByInput", Position = 0,
            ValueFromPipeline = $true)]
        [Object] $InputObject
    )

    begin 
    {
        ## Create the array to hold the content. If the user specified the
        ## -Path parameter, read the bytes from the path.
        [byte[]] $inputBytes = $null
        if($Path) { $inputBytes = [IO.File]::ReadAllBytes( (Resolve-Path $Path) ) }
       
        ## Store our header, and formatting information
        $counter = 0
        $header = " 0 1 2 3 4 5 6 7 8 9 A B C D E F"
        $nextLine = "{0} " -f  [Convert]::ToString(
            $counter, 16).ToUpper().PadLeft(8, '0')
        $asciiEnd = ""

        ## Output the header
        "`r`n$header`r`n"
    }

    process
    {
        ## If they specified the -InputObject parameter, retrieve the bytes
        ## from that input
        if($InputObject)
        {
            ## If it's an actual byte, add it to the inputBytes array.
            if($InputObject -is [Byte])
            {
                $inputBytes = $InputObject
            }
            else
            {
                ## Otherwise, convert it to a string and extract the bytes
                ## from that.
                $inputString = [string] $InputObject
                $inputBytes = [Text.Encoding]::Unicode.GetBytes($inputString)
            }
        }
       
        ## Now go through the input bytes
        foreach($byte in $inputBytes)
        {
            ## Display each byte, in 2-digit hexidecimal, and add that to the
            ## left-hand side.
            $nextLine += "{0:X2} " -f $byte

            ## If the character is printable, add its ascii representation to
            ## the right-hand side. Otherwise, add a dot to the right hand side.
            if(($byte -ge 0x20) -and ($byte -le 0xFE))
            {
               $asciiEnd += [char] $byte
            }
            else
            {
               $asciiEnd += "."
            }

            $counter++;

            ## If we've hit the end of a line, combine the right half with the
            ## left half, and start a new line.
            if(($counter % 16) -eq 0)
            {
               
                "$nextLine $asciiEnd"
                $nextLine = "{0} " -f [Convert]::ToString(
                    $counter, 16).ToUpper().PadLeft(8, '0')
                $asciiEnd = "";
            }
        }
    }

    end
    {
        ## At the end of the file, we might not have had the chance to output
        ## the end of the line yet. Only do this if we didn't exit on the 16-byte
        ## boundary, though.
        if(($counter % 16) -ne 0)
        {
           while(($counter % 16) -ne 0)
           {
              $nextLine += " "
              $asciiEnd += " "
              $counter++;
           }
           "$nextLine $asciiEnd"
        }

        ""
    }

     

    [Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

    Comments [0] | | # 
     Friday, November 20, 2009
    Friday, November 20, 2009 7:42:09 PM (Pacific Standard Time, UTC-08:00) ( )

    When you’re writing a script that depends on PowerShell Remoting, it’s often helpful to know that the remoting channel is open and will support the activities of your script.

    PowerShell has a Test-WSMan command, but that only verifies that a WSMan connection is possible. There are other scenarios you could be impacted by:

    • Not having permission on the remote machine
    • Misconfiguration of the PowerShell endpoint
    • Corrupted installation
    • (etc)

    As you dig deeper, you realize that the only way to really test the viability of the remoting channel is to just do something on it, and verify that you got the results you expected to. Since the implementation was so simple, we didn’t write a cmdlet for it. In retrospect, the concept is more difficult than the implementation, so we probably should have written it anyways. Here’s an example function that tests the remoting connection to a specific machine.

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028

    function Test-PsRemoting
    {
        param(
            [Parameter(Mandatory = $true)]
            $computername
        )
       
        try
        {
            $errorActionPreference = "Stop"
            $result = Invoke-Command -ComputerName $computername { 1 }
        }
        catch
        {
            Write-Verbose $_
            return $false
        }
       
        ## I've never seen this happen, but if you want to be
        ## thorough....
        if($result -ne 1)
        {
            Write-Verbose "Remoting to $computerName returned an unexpected result."
            return $false
        }
       
        $true   
    }

    Comments [0] | | # 
     Thursday, October 29, 2009
    Thursday, October 29, 2009 6:10:58 PM (Pacific Daylight Time, UTC-07:00) ( )

    Over the past few years, Pumpkin carving in my family has somehow ended up focusing on two themes: Math, and Knitting.

    A Sierpinski Triangle -- which surprisingly only took a toothpick or two to repair isolated triangles:

    Sierpinski Carpet (along with a wee bit of evil, of course:)

     

    Not being one to cut 64 of the level-three squares by hand, a cordless drill came in extremely handy.

    Mandelbrot, and Koch snowflake:

    030 112

     

    Knitting randoms:

    106 109

    And just a cool cat in a window:

    Picture 636

    Unfortunately, when I'm out trick-or-treating, there's nobody around to give candy to the little monsters.  I leave a note above a bowl on a chair – and now I finally know why my calligraphy pens include red in the set!

     

    Comments [1] | | # 
     Tuesday, October 27, 2009
    Wednesday, October 28, 2009 3:28:01 AM (Pacific Daylight Time, UTC-07:00) ( )

    One thing you might notice if you have an iPhone connecting to an Inbox via the IMAP protocol is that messages you delete tend to stick around when viewed from other devices (such as Outlook, Outlook Web Access, etc.)

    This is caused by an out-of-date view of mail management, where your Inbox handles everything. When you delete an item, some IMAP clients (such as the iPhone) mark them as deleted, but don’t actually remove the item from the server. Some clients hide these deleted items. Some show them with a line through them. Some ignore the deleted flag altogether.

    Most email clients (including iPhone) move deleted items to their own sub-folder, so marking items as deleted just ends up being an annoyance:

    http://discussions.apple.com/thread.jspa?messageID=4934598
    http://blogs.freshlogicstudios.com/Posts/View.aspx?Id=44a01293-3b32-4ee0-b23c-fac99348e1cd
    http://www.robichaux.net/blog/2007/07/iphone_vs_windows_mobile_part_3_mail.php

    The solution is the IMAP ‘EXPUNGE’ command. It permanently deletes items that have been marked for deletion (while of course leaving everything in Deleted Items untouched.) The iPhone has an option to do this once a day, but it doesn’t seem to work very well (and a day lag feels like an eternity, anyways.)

    The folks at freshlogic have a fine application to run the command against your mail server. We can improve on it in a few ways by writing it in PowerShell. Our version:

    • Enables support for securely cached credentials
    • Doesn’t require an always-running application
    • Runs in the background

    To do this, our script imports our saved password, and then uses Send-TcpRequest to simply connect to the server and send the EXPUNGE command. Finally, we use schtasks.exe to automate it.

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    # 1) Get your password into a securestring:
    # $ss = Read-Host -AsSecureString
    # 2) Export it to disk
    # $ss | ConvertFrom-SecureString |
    # Out-File (Join-Path (Split-Path $profile) Sync-MailboxDeletedState.ps1.credential)
    # 3) Create the scheduled task
    # schtasks /Create /TN MailboxUpdater /SC MINUTE /MO 5 /TR
    # "powershell -NoProfile -WindowStyle Hidden -File 'c:\path\to\Sync-MailboxDeletedState.ps1'"

    $username = "user@example.com"
    $server = "imap.example.com"
    $port = 993

    $passwordPath = Join-Path (Split-Path $profile) Sync-MailboxDeletedState.ps1.credential
    $password = Get-Content $passwordPath | ConvertTo-SecureString

    $cred = New-Object System.Management.Automation.PsCredential $username,$password

    $nc = $cred.GetNetworkCredential()
    $commands = "A1 LOGIN $($nc.UserName + '@' + $nc.Domain) $($nc.Password)",
                'A2 SELECT INBOX',
                'A3 EXPUNGE',
                'A4 LOGOUT'

    $commands | Send-TcpRequest $server $port -UseSSL
    Comments [0] | | # 
    Wednesday, October 28, 2009 2:51:58 AM (Pacific Daylight Time, UTC-07:00) ( )

    Awhile back, I introduced a script that allows you interact with remote TCP ports (such as Telnet.) While useful, it worked only interactively. It would be even more useful if you were able to script a network or TCP connection.

    Let me introduce Send-TcpRequest.ps1 v2, which allows exactly that:

    First, a simple scripted HTTP session:

    $http = @"
    GET / HTTP/1.1
    Host:search.msn.com
    `n`n
    "@

    $http | Send-TcpRequest search.msn.com 80

    Second, a scripted POP3 session (Parse-TextObject comes from here: http://www.leeholmes.com/blog/parsetextObjectAWKWithAVengeance.aspx):

    if(-not (test-path Variable:\mailCredential))
    {
       $mailCredential = Get-Credential
    }
    $address = $mailCredential.UserName
    $password = $mailCredential.GetNetworkCredential().Password
    $pop3Commands = "USER $address","PASS $password","STAT","QUIT"
    $output = $pop3Commands | Send-TcpRequest mail.leeholmes.com 110
    $inbox = $output.Split("`n")[3]
    $status = $inbox | Parse-TextObject -PropertyName "Response","Waiting","BytesTotal","Extra"
    "{0} messages waiting, totaling {1} bytes." -f $status.Waiting,$status.BytesTotal

     

    Now, here is Send-TcpRequest.ps1

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    ##############################################################################
    ## Send-TcpRequest.ps1
    ##
    ## From Windows PowerShell Cookbook (O'Reilly)
    ## by Lee Holmes (http://www.leeholmes.com/guide)
    ##
    ## Send a TCP request to a remote computer, and return the response.
    ## If you do not supply input to this script (via either the pipeline, or the
    ## -InputObject parameter,) the script operates in interactive mode.
    ##
    ## Example:
    ##
    ## $http = @"
    ## GET / HTTP/1.1
    ## Host:search.msn.com
    ## `n`n
    ## "@
    ##
    ## $http | Send-TcpRequest search.msn.com 80
    ##############################################################################
    param(
            [string] $remoteHost = "localhost",
            [int] $port = 80,
            [switch] $UseSSL,
            [string] $inputObject,
            [int] $commandDelay = 100
         )

    [string] $output = ""

    ## Store the input into an array that we can scan over. If there was no input,
    ## then we will be in interactive mode.
    $currentInput = $inputObject
    if(-not $currentInput)
    {
        $SCRIPT:currentInput = @($input)
    }
    $scriptedMode = [bool] $currentInput

    function Main
    {
        ## Open the socket, and connect to the computer on the specified port
        if(-not $scriptedMode)
        {
            write-host "Connecting to $remoteHost on port $port"
        }

        trap { Write-Error "Could not connect to remote computer: $_"; exit }
        $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port)

        if(-not $scriptedMode)
        {
            write-host "Connected. Press ^D followed by [ENTER] to exit.`n"
        }

        $stream = $socket.GetStream()
       
        if($UseSSL)
        {
            $sslStream = New-Object System.Net.Security.SslStream $stream,$false
            $sslStream.AuthenticateAsClient($remoteHost)
            $stream = $sslStream
        }

        $writer = new-object System.IO.StreamWriter $stream

        while($true)
        {
            ## Receive the output that has buffered so far
            $SCRIPT:output += GetOutput

            ## If we're in scripted mode, send the commands,
            ## receive the output, and exit.
            if($scriptedMode)
            {
                foreach($line in $currentInput)
                {
                    $writer.WriteLine($line)
                    $writer.Flush()
                    Start-Sleep -m $commandDelay
                    $SCRIPT:output += GetOutput
                }

                break
            }
            ## If we're in interactive mode, write the buffered
            ## output, and respond to input.
            else
            {
                if($output) 
                {
                    foreach($line in $output.Split("`n"))
                    {
                        write-host $line
                    }
                    $SCRIPT:output = ""
                }

                ## Read the user's command, quitting if they hit ^D
                $command = read-host
                if($command -eq ([char] 4)) { break; }

                ## Otherwise, Write their command to the remote host
                $writer.WriteLine($command)
                $writer.Flush()
            }
        }

        ## Close the streams
        $writer.Close()
        $stream.Close()

        ## If we're in scripted mode, return the output
        if($scriptedMode)
        {
            $output
        }
    }

    ## Read output from a remote host
    function GetOutput
    {
        ## Create a buffer to receive the response
        $buffer = new-object System.Byte[] 1024
        $encoding = new-object System.Text.AsciiEncoding

        $outputBuffer = ""
        $foundMore = $false

        ## Read all the data available from the stream, writing it to the
        ## output buffer when done.
        do
        {
            ## Allow data to buffer for a bit
            start-sleep -m 1000

            ## Read what data is available
            $foundmore = $false
            $stream.ReadTimeout = 1000

            do
            {
                try
                {
                    $read = $stream.Read($buffer, 0, 1024)

                    if($read -gt 0)
                    {
                        $foundmore = $true
                        $outputBuffer += ($encoding.GetString($buffer, 0, $read))
                    }
                } catch { $foundMore = $false; $read = 0 }
            } while($read -gt 0)
        } while($foundmore)

        $outputBuffer
    }

    . Main

    [Edit: Thanks to Marco for pointing out a few issues that come up when you try to retrieve massive amounts of data (such as a newsgroup listing). I've updated the script to fix those.]
    [Edit2: Updated to call it Send-TcpRequest, and support SSL]

    Comments [6] | | # 
     Tuesday, October 20, 2009
    Tuesday, October 20, 2009 5:07:26 PM (Pacific Daylight Time, UTC-07:00) ( )

     

    Download: http://www.leeholmes.com/projects/ActivityTracker/ActivityTracker.zip

    Over the summer, The Scripting Guys ran an excellent series for the 2009 Summer Scripting Games. They asked me to be a guest commentator for an event, and it turns out that it was something I’d been toying with in one version or another for some time.

    The topic of this event was an Activity Tracker. In your work life, it is incredibly helpful to know how you spend your time. Personally, it greatly helps improve your estimation skills: did you really spend as much time on the project as you thought you would? Professionally, it helps you remember important events for a given time period. For example, pulling status reports together for a manager, or reviewing your year’s accomplishments in preparation for your yearly review.

    I’ve been using an activity tracker in one way or the other for several years now, and definitely consider it a core tool / technique.

     

    Activity Tracker

    A light-weight personal productivity tool

    Activity Tracker helps you analyze your time by infrequently asking the simple question: “What are you doing?

    clip_image002

    Installation

    1)      Copy Start-ActivityTracker.ps1 to your local computer

    2)      Install the PowerBoots UI scripting library into your Modules directory (http://powerboots.codeplex.com). Note: PowerBoots includes a “Functions” directory. Delete everything in it before launching the first time.

    3)      Place a shortcut to the following command in your Startup folder:
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command Start-ActivityTracker -AsApplication

    4)      (First time only) Launch the shortcut from your Startup folder

    How it Works

    Activity Tracker follows the same principles as a traditional software sampling profiler, but instead samples humans. By randomly recording your current task, Activity Tracker lets you analyze your answers as a fairly faithful proxy for how you actually spent your time. If 20% of your answers were “Status Meeting,” then you spent close to 20% of your time in status meetings.

    An Alternative to Sampling

    An alternative to the sampling approach is an instrumentation approach: faithfully recording your transition between tasks. Activity Tracker avoids this design, since asking humans to faithfully record transitions between tasks is enormously error-prone. For example, you might not log a task transition for a task that you consider inconsequential (for example, “Checking email”,) when in fact that task may account for a significant portion of your day. Some software attempts to address the human element by tracking window titles, but the level of data captured by window titles often does not map well to the task they support.

    Using Activity Tracker

    Activity Tracker is a PowerShell script. It spawns a new instance of PowerShell to run itself, but also lets you specify the –AsApplication flag if you want it to have a unique name for the resulting exe. This new executable is simply a copy of PowerShell.exe.

    Once launched, Activity Tracker sits in the background. Once in awhile (randomly, between 5 and 25 minutes,) it asks you the question, “What are you doing?” It stores your previous answers in a list until you exit the program, which lets you easily re-use your answers to previous questions.

    When you press OK, it adds your answer (along with the current window title) to a file in “My Documents\ActivityTracker” – one file per week. The file is named to correspond to the date on the first day of the week.

    If you don’t answer within four minutes, it dismisses the dialog and checks your Outlook calendar. If you are in a meeting, it records the title of that meeting. If you aren’t in a meeting, it records nothing. This lets you keep the Activity Tracker running when you go home for the day without polluting your journal files.

    Slicing and Dicing

    The Activity Tracker records its output as a simple CSV file. Knowing that, you can slice and dice results to your heart’s content. For example, to easily get a summary of your week:

                                                                                                                            
    PS >Import-Csv temp.csv | Group Activity | Sort -Descending Count         
                                                                              
    Count Name                      Group                                     
    ----- ----                      -----                                     
       23 Hubble Space Telecsope... {@{Date=5/20/2009 8:24:19 AM; WindowTi...
        8 Meeting: Design review    {@{Date=5/20/2009 1:10:21 PM; WindowTi...
        5 Meeting: Team meeting     {@{Date=5/20/2009 3:10:20 PM; WindowTi...
        4 Email                     {@{Date=5/20/2009 8:04:26 AM; WindowTi...
        3 Scripting games           {@{Date=5/19/2009 6:09:16 PM; WindowTi...
                                                                                                                                                   <?xml:namespace prefix = o />

     

    To count how many hours you spent on a task, simply divide by four.

    Dependencies

    Activity tracker uses the PowerBoots UI scripting library: http://powerboots.codeplex.com/.

    Comments [0] | | #