PowerShell Cookbook

Search

Categories

 

On this page

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
PowerShell Cryptography Library

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: 257
This Year: 8
This Month: 2
This Week: 0
Comments: 785

Sign In

 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 [5] | | # 
 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 [7] | | # 
 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] | | # 
 Monday, October 19, 2009
Monday, October 19, 2009 8:28:22 PM (Pacific Daylight Time, UTC-07:00) ( )

When playing with cryptography challenges (don’t we all?,) you end up leaning on a bunch of common tasks. For example, substituting all text in a string with a set of replacements (substitution ciphers,) XORing strings together, applying dictionary-based algorithms, investigating word frequency, and more.

PowerShell lends itself really well to these challenges, and I’ve developed a small cryptography library along the way. Here it is. Your job, as a cryptographer, is to uncover the hidden comments :) (Hint: don’t spend very long. Some problems are unbreakable.)

For example, here’s the script I used (interactively) to solve an “XOR Cipher” at http://cryptolib.com/challenges.php.

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
$cipherHex1 = @"
3526532A122A183D4D0E4A2E3713101954013E2B3E2246235728
573553064832781F164A481F2A2B396350341E2B5E3A5E1C4861
301C11544F1D22363863413F192A4A36520A0D2D2D13044A001A
252025335E2F142F5A355A
"@


$cipherHex2 = @"
32335734163A5136510E41612818114A4912252C2722462F1820
4B7956014E283C180D4D001522312724573416225C794C074C2C
3D0E435A4F063B29383012251B214C315A1C41283618435E5212
2531342D556612205B2B461F592837131019481225212F225B2A
"@


$words = @{}
Get-Content dictfull.txt | % { $words[$_] = $true }

## Turn the cipher text into a single string
$cipherText1 = $cipherHex1 -replace "`r`n",""
$cipherText2 = $cipherHex2 -replace "`r`n",""

## Convert hex to decimal
$cipherBytes1 = $cipherText1 -split '(..)' | ? { $_ } | % { [Convert]::ToInt32($_, 16) }
$cipherBytes2 = $cipherText2 -split "(..)" | ? { $_ } | % { [Convert]::ToInt32($_, 16) }

## Drop the key by XORing the two cipher texts together
$plainTextCombined = XORBytes $cipherBytes1[0..19] $cipherBytes2[0..19]

## Initialize the key
$key = ,0x20*20

## First break comes by scanning through the entire dictionary, guessing the
## first word of one plaintext, then seeing if that corresponds to a key that makes
## the second plaintext look reasonable
$output = $(foreach($word in $words.GetEnumerator() | % { $_.Key })
{
    $testString = XORBytes $plainTextCombined[0..($word.Length - 1)] ($word.ToCharArray())
    (-join [char[]]$testString) + " : $word"
}
)

## Output the list to a file, then clean the file to include only alpha-numeric
## characters.
$output > first_word.txt
$alphaOnly = gc first_word.txt | select-string '^[a-zA-Z ]+ : .*$'
$alphaOnly > first_word_alpha.txt

## The first break comes from seeing that "healed dr" corresponds to "operation".
## Make a guess of that as the first 9 elements of the key.
$key[0],$key[1],$key[2],$key[3],$key[4],$key[5],$key[6],$key[7],$key[8] = XORBytes ("healed dr".ToCharArray()) $cipherBytes1[0..8]

## These other magic values come from looking at the output and making educated guesses
$key[9],$key[10] = XORBytes ("se".ToCharArray()) $cipherBytes1[49..50]
$key[11],$key[12],$key[13] = XORBytes ("ion".ToCharArray()) $cipherBytes2[91..93]
$key[14],$key[15],$key[16] = XORBytes ("gs ".ToCharArray()) $cipherBytes1[74..76]
$key[17],$key[18],$key[19] = XORBytes ("Fit".ToCharArray()) $cipherBytes2[37..39]

## Display the currently-guessed plain texts alongside eachother,
## including the key and index into the cipher text.
for($counter = 0; $counter -lt $cipherBytes2.Count; $counter++)
{
    $keyChar = $key[$counter % 20]
    "{0} {1} {2} {3}" -f $counter,([char] $keyChar),([char] ($cipherBytes1[$counter] -bxor $keyChar)),([char] ($cipherBytes2[$counter] -bxor $keyChar))
}

-join [char[]] (XORBytes $key $cipherBytes1)
-join [char[]] (XORBytes $key $cipherBytes2)
-join [char[]] $key

 

And the library:

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
function Get-SubstitutedText($cipherText, $substitutions)
{
    $content = $(foreach($char in [char[]] $cipherText)
    {
        if($substitutions["$char"])
        {
            $substitutions["$char"]
        }
        else
        {
            "."
        }
    }
    )
   
    -join $content
}

function XORBytes($passwordBytes, $cipherBytes2)
{
    $combinedBytes = @()
    for($counter = 0; $counter -lt $cipherBytes2.Length; $counter++)
    {
        $combinedBytes += $passwordBytes[$counter % $passwordBytes.Length] -bxor $cipherBytes2[$counter]
    }
   
    $combinedBytes
}

function Search-DictionaryForWord($pattern, $floating)
{
    if(-not $floating) { $pattern = "^$pattern`$" }
    Select-String $pattern wordlist.txt
}

function Search-DictionaryForPattern($pattern, $floating)
{
    $substituted = ""
    $toFind = ""
   
    if(-not $floating) { $toFind += "^" }
   
    foreach($char in $pattern.ToLower().ToCharArray())
    {
        if($char -eq ".")
        {
            $toFind += "."
            continue
        }
       
        $subIndex = $substituted.IndexOf($char)
       
        if($subIndex -ge 0)
        {
            $toFind += "\" + ($subIndex + 1)
        }
        else
        {
            if(-not $substituted)
            {
                $toFind += "(.)"
            }
            else
            {
                $toFind += "([^$substituted])"
            }
            $substituted += $char
        }
    }
   
    if(-not $floating) { $toFind += '$' }
    Write-Verbose $toFind
    Select-String $toFind wordlist.txt
}


function Measure-LetterFrequency($cipherText)
{
    $frequentLetters = " etnoriasfindhdlcumwfgypbvkjxqz"

    "`nLetter Frequencies:`n"
    $groups = [char[]] $cipherText.ToLower() | group | Sort -Desc Count

    $groupNumber = 0
    $(foreach($group in $groups)
    {
        $group | Select Name,
            @{
                Name = "Replacement";
                Expression = { $frequentLetters[$groupNumber] } },
            Count,
            @{
                Name = "Percent";
                Expression = { "{0:..%}" -f ($_.Count / $cipherText.Length) } }
        $groupNumber++  
    }) | ft -auto | out-string | % { $_.Trim() }
}

function Measure-BigraphFrequency($cipherText)
{
    $frequentBigraphs = "th","he","an","in","er","on","re",
        "ed","nd","ha","at","en","es","of","nt","ea","ti",
        "to","io","le","is","ou","ar","as","de","rt","ve"
       
    $cipherText = $cipherText -replace " ",""
    $cipherText = $cipherText -replace "\W",""

    "`nBigraph Frequencies:`n"
    $groups = 
        @(($cipherText -split '(..)') +
          ($cipherText.Remove(0,1) -split '(..)')) |
          ? { $_.Trim().Length -eq 2 } | group | Sort -Desc Count

    $groupNumber = 0
    $(foreach($group in $groups)
    {
        $group | Select Name,
            @{
                Name = "Replacement";
                Expression = { $frequentBigraphs[$groupNumber] } },
            Count
        $groupNumber++  
    }) | ft -auto | out-string | % { $_.Trim() }
}

function Measure-DoubleLetterFrequency($cipherText)
{
    $frequentDoubles = "ss","ee","tt","ff","ll","mm","oo"
       
    "`nDouble Frequencies:`n"
    $groups = [Regex]::Matches($cipherText, '(.)\1') | % { $_.Value } |
        group | Sort -Desc Count

    $groupNumber = 0
    $(foreach($group in $groups)
    {
        $group | Select Name,
            @{
                Name = "Replacement";
                Expression = { $frequentDoubles[$groupNumber] } },
            Count
        $groupNumber++  
    }) | ft -auto | out-string | % { $_.Trim() }
   
    "`nOther groups:"
    "$($frequentDoubles[$groupNumber..($frequentDoubles.Length - 1)])"
}

function Measure-InitialFrequency($cipherText)
{
    $frequentInitialLetters = "toawbcdsfmrhiyeglnoujk"

    "`nInitial letters:`n"
    $groups = -split $cipherText.ToLower() | % { ([char[]] $_)[0] } | group | Sort -Desc Count

    $groupNumber = 0
    $(foreach($group in $groups)
    {
        $group | Select Name,
            @{
                Name = "Replacement";
                Expression = { $frequentInitialLetters[$groupNumber] } },
            Count,
            @{
                Name = "Percent";
                Expression = { "{0:..%}" -f ($_.Count / $groups.Count) } }
        $groupNumber++  
    }) | ft -auto | out-string | % { $_.Trim() }
}

function combo($string)
{
    if($string.Length -eq 2)
    {
        $string
        $string[-1] + $string[0]
    }
    else
    {
        foreach($element in $string.ToCharArray())
        {
            combo ($string -replace $element,'') | foreach { $element + $_ }
            combo ($string -replace $element,'') | foreach { $_ }
        }
    }
   
}
Comments [0] | | #