Thursday, February 28, 2008

2008 Scripting Games - Advanced Event 8

 

Event 8: Making Beautiful Music

 

$TotalMusicTime = New-TimeSpan

$songList = cat C:\Scripts\songlist.csv | foreach {
    $item = $_.split(",")
    $time = $item[2].split(":")
    $song = new-object psobject
    add-member -inp $song noteproperty Artist $item[0]
    add-member -inp $song noteproperty SongName $item[1]
    add-member -inp $song noteproperty Minutes $time[0]
    add-member -inp $song noteproperty Seconds $time[1]
    $song    
}

$cd=@()

$singleArtist = $songList | group Artist | where {$_.count -eq 1}
$groupsArtist = $songList | group Artist | where {$_.count -gt 1}
$singleArtist | foreach {
    $song = new-object psobject
    add-member -inp $song noteproperty Artist $_.Group[0].Artist
    add-member -inp $song noteproperty SongName $_.Group[0].SongName
    add-member -inp $song noteproperty Minutes $_.Group[0].Minutes
    add-member -inp $song noteproperty Seconds $_.Group[0].Seconds
    $cd+=$song
    $TotalMusicTime = $TotalMusicTime.add((New-TimeSpan -Minutes $song.Minutes -Seconds $song.Seconds))
}

$groupsArtist | foreach {
    if(($TotalMusicTime.TotalMinutes -ge 75) -and ($TotalMusicTime.TotalMinutes -le 80)){return}
    $song = new-object psobject
    add-member -inp $song noteproperty Artist $_.Group[0].Artist
    add-member -inp $song noteproperty SongName $_.Group[0].SongName
    add-member -inp $song noteproperty Minutes $_.Group[0].Minutes
    add-member -inp $song noteproperty Seconds $_.Group[0].Seconds
    $cd+=$song
    $TotalMusicTime = $TotalMusicTime.add((New-TimeSpan -Minutes $song.Minutes -Seconds $song.Seconds))
}

write-host
$cd | select Artist,SongName,@{n="Time";e={"{0}:{1}" -f $_.Minutes,$_.Seconds}} | sort artist | ft -a

"Total music time: {0}:{1}" -f $TotalMusicTime.TotalMinutes.ToString("N0"),$TotalMusicTime.Seconds
write-host

 

Artist                      SongName                     Time
------                      --------                     ----
Alice Cooper                I'm Eighteen                 2:58
Badfinger                   No Matter What               2:59
Cracker                     Eurotrash Girl               8:03
Credence Clearwater Revival Have You Ever Seen the Rain? 2:39
Dire Straits                So Far Away                  5:12
Don McLean                  American Pie                 8:35
Donovan                     Catch the Wind               5:02
Jefferson Airplane          White Rabbit                 2:34
John Prine                  New Train                    3:24
Nick Cave and the Bad Seeds Deanna                       3:46
Nirvana                     Heart-Shaped Box             4:41
Paul Simon                  Mother and Child Reunion     2:48
REM                         Losing My Religion           4:28
Robert Palmer               Simply Irresistible          4:12
The Animals                 House of the Rising Sun      4:31
The Bangles                 Manic Monday                 3:06
The Beatles                 Help                         2:18
The Kinks                   Sunny Afternoon              3:36
The Ramones                 I Wanna Be Sedated           2:29


Total music time: 77:21

2008 Scripting Games - Advanced Event 7

 

$teams = "A","B","C","D","E","F"
$rounds=@()

for($i=0; $i -lt $teams.length; $i++){
   0..2 | foreach { $rounds += ("{0} vs. {1}" -f $teams[$_],$teams[-($_+1)]) }
   1..($teams.length-1) | foreach { $teams[1],$teams[$_] = $teams[$_],$teams[1] }
} 

# remove duplicates
$rounds = $rounds | select -unique

# random positions
$rnd = new-object random (get-date).millisecond 
$list = new-object System.Collections.ArrayList
$list.AddRange(0..($rounds.count-1))

1..$rounds.count | foreach {
   $n=$rnd.next($list.count)	
   $rounds[$list[$n]]
   [void]$list.removeAt($n)
}



#result

B vs. E
A vs. C
C vs. D
A vs. E
F vs. D
C vs. F
D vs. B
F vs. B
B vs. C
A vs. D
A vs. B
A vs. F
E vs. F
E vs. C
D vs. E

Sudden Death Challenge: Event 7

 

([math]::Sqrt(([math]::Pow(([math]::Truncate((100*2)/30)),5) *4)) /45).toString("N2")

 

#Result

3.92

Sudden Death Challenge: Event 6

 

$words = (cat c:\scripts\lettercase.txt).split(" ") | foreach {
    if($_ -match '\d+') {
        $_= [int]$_ -1111; $_
    } else {
        $_= $_[0].toString().toUpper()+$_.substring(1).toLower(); $_
    }
}

[string]::join(" ",$words)

 

# result

The 2008 Winter Scripting Games!

Sudden Death Challenge: Event 5

 

# one line

gwmi -list | ? {$_.name -like "WIN32_*"} | % {$_.psbase.properties} | ? {($_.name[0] -cmatch '[A-Y]') -and ($_.origin -like "WIN32_*")} | group {$_.name[0]} | sort name | select @{n="Name";e={$_.group[0].name}},@{n="Class";e={$_.group[0].origin}} | ft -auto


# results
Name                      Class                               
----                      -----                               
ArpAlwaysSourceRoute      Win32_NetworkAdapterConfiguration   
Bias                      Win32_TimeZone                      
ColorPlanes               Win32_DisplayControllerConfiguration
DriveName                 Win32_VolumeChangeEvent             
EventType                 Win32_PowerManagementEvent          
FileName                  Win32_ModuleLoadTrace               
GatewayCostMetric         Win32_NetworkAdapterConfiguration   
HorizontalResolution      Win32_DisplayControllerConfiguration
ImageBase                 Win32_ModuleLoadTrace               
JavaClass                 Win32_ClassicCOMClassSetting        
KeepAliveInterval         Win32_NetworkAdapterConfiguration   
LastDrive                 Win32_BootConfiguration             
MachineName               Win32_ComputerSystemEvent           
NumForwardPackets         Win32_NetworkAdapterConfiguration   
OEMEventCode              Win32_PowerManagementEvent          
ProcessID                 Win32_ModuleLoadTrace               
QuotaNonPagedPoolUsage    Win32_Process                       
RemainingEvaluationPeriod Win32_WindowsProductActivation      
StackBase                 Win32_ThreadStartTrace              
Type                      Win32_ComputerShutdownEvent         
UserStackBase             Win32_ThreadStartTrace              
VerticalResolution        Win32_DisplayControllerConfiguration
WaitMode                  Win32_ThreadStartTrace              
XOffCharacter             Win32_SerialPortConfiguration       
YResolution               Win32_PrinterConfiguration          


Sudden Death Challenge: Event 3

 

$presidents= cat C:\Scripts\presidents.txt | foreach {
    $tmp = $_.split(",")
    $p=New-Object psobject
    Add-Member -inp $p noteproperty firstName $tmp[1].trim()
    Add-Member -inp $p noteproperty lastName  $tmp[0].trim()
    Add-Member -inp $p noteproperty firstLength $p.firstName.length
    Add-Member -inp $p noteproperty vowels @(("{0}{1}" -f $p.firstName,$p.lastName).toCharArray() -match '[aeiou]').count
    $p
}


$longest = $presidents | sort firstLength -desc | select -first 1
"Longest first name: {0} {1}" -f $longest.firstName,$longest.lastName

"Total vowels used: {0}" -f ($presidents | measure-object vowels -sum).sum


$initials = ""
$alphabet = ([string](65..90 | foreach {[char]$_})).replace(" ","")

$presidents  | foreach {
    $initials+="{0}{1}" -f $_.firstName[0],$_.lastName[0]
}

$alphabet = $alphabet -replace "[$initials]"
"The following letters are not used as Presidential initials:"
$alphabet.ToCharArray()

 

 

# results

Longest first name: Rutherford Hayes
Total vowels used: 192
The following letters are not used as Presidential initials:
I
O
Q
S
X
Y

Sudden Death Challenge: Event 2

 
$vertical = @(cat C:\Scripts\vertical.txt) 
$text=""

for($i=0; $i -le 3; $i++){
    0..($vertical.length-1) | foreach { $text+=$vertical[$_][$i] } 
}

$text
 
# result
The 2008 Winter Scripting Games!

Sudden Death Challenge: Event 4

 

$s = "6882463283678273808479"
for($i=0; $i -lt ($s.length/2); $i++) {write-host -noNew ([char][int]$s.substring(($i*2),2))}

 

#result

DR. SCRIPTO

Wednesday, February 27, 2008

PowerGUI 1.0.14 is available

 

Go get it!

2008 Scripting Games - Advanced Event 6

 

Event 6: Prime Time

 

function Test-Prime ($num){ 

  $prime = new-object psobject
  add-member -inp $prime noteproperty Number $null
  add-member -inp $prime noteproperty IsPrime $true

  $prime.Number=$num
  
  if($num -eq 2) {
    $prime.IsPrime=$true
  } else {
    foreach ($i in 2..([math]::Sqrt($num))) {
      if ($num % $i -eq 0) {$prime.IsPrime=$false }
    }
  }
  
  $prime  
}
  
1..200 | foreach {Test-Prime $_} | Where {$_.IsPrime} | select number | ft -auto
 

Number
------
     2
     3
     5
     7
    11
    13
    17
    19
    23
    29
    31
    37
    41
    43
    47
    53
    59
    61
    67
    71
    73
    79
    83
    89
    97
   101
   103
   107
   109
   113
   127
   131
   137
   139
   149
   151
   157
   163
   167
   173
   179
   181
   191
   193
   197
   199

2008 Scripting Games - Advanced Event 5

Event 5: You Call That a Strong Password?

param(
  [string]$pwd = $(throw "Please specify password"),
  [string]$wordList = "c:\scripts\WordList.txt"

)

$score=13

$ptrn6 = '(.*).{10,20}' ; if($pwd -notmatch $ptrn6) {$score-=1; "Password is less than 10 characters or less than 20 characters"}


$ptrn7 = '\d' ; if($pwd -notmatch $ptrn7) {$score-=1; "Password must contain at least one number"} $ptrn8 = '[A-Z]' ; if($pwd -cnotmatch $ptrn8) {$score-=1; "No uppercase letters in password."} $ptrn9 = '[a-z]' ; if($pwd -cnotmatch $ptrn9) {$score-=1; "No lowercase letters in password."} $ptrn10 = '(?=.*[^A-Za-z0-9])' ; if($pwd -notmatch $ptrn10) {$score-=1; "Password must contain at least one symbol"} $ptrn11 = '[a-z]{4,}' ; if($pwd -cmatch $ptrn11) {$score-=1; "Four consecutive lowercase letters in password."} $ptrn12 = '[A-Z]{4,}' ; if($pwd -cmatch $ptrn12) {$score-=1; "Four consecutive uppercase letters in password."} if ($pwd.ToCharArray() | group | where {$_.count -gt 1}) {$score-=1; "Duplicate letters in password."} $words = cat $wordList if($words -contains $pwd) {$score-=1; "Password can't be an actual word"} if($words -contains $pwd.substring(0,($pwd.length-1))) {$score-=1; "Password minus the last letter, is an actual word"} if($words -contains $pwd.substring(1)) {$score-=11; "Password minus the first letter, is an actual word"} if($words -contains ($pwd -replace 'O','0')) {$score-=1; "Password substitute 0 (zero) for the letter o (either an uppercase O or a lowercase o)"} if($words -contains ($pwd -replace 'L','1')) {$score-=1; "Password does not simply substitute 1 (one) for the letter l (either an uppercase L or a lowercase l)"} Write-Host switch($score){ {$_ -lt 7} {"A password score of $score indicates a weak password."; break} {($_ -ge 7) -and ($_ -le 10)} {"A password score of $score indicates a moderately-strong password"; break} {$_ -ge 11} {"A password score of $score indicates a strong password."; break} }

PowerGUI 1.0.14

 

pgui

PowerGUI's next version is on its way. If you haven't heard of it (shame on ya), PowerGUI is an extensible graphical administrative console and IDE for managing systems based on Windows PowerShell and it's FREE for download.

 

 

Here's a partial list of the new features, (PowerGUI Feature Map):

- A lot of useful snippets in the editor.
- Pipeline stepping WOW! - When debugging scripts, you can step inside one-liners, switch statements etc.
- Updated Exchange PowerPack and improved default PowerPacks.
- Improved startup performance.
- Automated rollback of configuration for PowerGUI and Editor.

 

Personally, I love it and use it a lot on a daily basis. It has all you can expect from an IDE editor/debugger. PowerGUI forums is a great place to start if you're having questions about it.

So, what are waiting for? Point your browser to PowerGUI.org and download the latest build.

Marco Shaw Does More With PowerShell!

 

RunAs Radio is a weekly Internet Audio Talk Show for IT Professionals working with Microsoft products. The full range of IT topics is covered from a Microsoft-centric viewpoint.

The current show,  #46 (30 minutes) is with Marco Shaw [MVP].

Richard and Greg talk to Marco Shaw about the growth of PowerShell in the Windows server community. Marco's blog provides info on PowerShell and his virtual user group meetings on PowerShell. We also dig into the role of WMI in PowerShell and Microsoft's focus on making PowerShell a key tool for managing all Microsoft products in the future.

Monday, February 25, 2008

My semi Tower of Power

 

My colleague came back from the States today, He brought the bottom two with him.

top

Put Some Muscle in Your Management

 

I've just received my Windows IT Pro Magazine current issue. PowerShell is featured in the cover story with PowerShell 101, Lesson 1 and Alex Angelopoulos shares his views on Windows PowerShell in PowerShell Empowerment.

2008 Scripting Games - Advanced Event 4

Event 4: Image is Everything

 

function print-calendar($dte){
  
  $month,$year = $dte.split("/")
  $dtfi = new-object system.globalization.datetimeformatinfo
  $AbbreviatedDayNames=$dtfi.AbbreviatedDayNames | foreach {" {0}" -f $_}

  $header= "$($dtfi.MonthNames[$month-1]) $year"
  write-host $header
  
  write-host ([string]::join("",$AbbreviatedDayNames))
  $daysInMonth=[datetime]::DaysInMonth($year,$month)

  $dayOfWeek =(new-object datetime $year,$month,1).dayOfWeek.value__
  $today=(get-date).day
  
  for ($i = 0; $i -lt $dayOfWeek; $i++){write-host (" "*4) -nonew}
  for ($i = 1; $i -le $daysInMonth; $i++)
  {
    if($today -eq $i){write-host ("{0,4}" -f $i) -nonew}
    else {write-host ("{0,4}" -f $i) -nonew}

      if ($dayOfWeek -eq 6) {write-host}
      $dayOfWeek = ($dayOfWeek + 1) % 7
  }
  if ($dayOfWeek -ne 0) {write-host}
}

print-calendar 2/2008
 
February 2008
 Sun Mon Tue Wed Thu Fri Sat
                       1   2
   3   4   5   6   7   8   9
  10  11  12  13  14  15  16
  17  18  19  20  21  22  23
  24  25  26  27  28  29
 
 
 
 
 
 

2008 Scripting Games - Advanced Event 3

 

Event 3: Instant (Runoff) Winner

$ballots = cat C:\Scripts\votes.txt | foreach {
  $tmp = $_.split(",")
  $ballot = New-Object psobject
  add-member -inp $ballot noteproperty candidat $tmp
  $ballot
}


$totalBallots = $ballots.length

for($i=1; $i -lt 4; $i++){
  $round = $ballots | group {$_.candidat[0]} | sort count -desc | select Name, @{n="Percent";e={"{0:N2}" -f (($_.count/$totalBallots)*100)}}
  
  $first = $round[0]
  if($first.Percent -ge 50){
    "The winner is {0} with {1}% of the vote." -f $first.Name,$first.Percent
    return
  }
  
  $last = $round[-1].Name
  $ballots | foreach {$_.candidat = $_.candidat | where {$_ -ne $last}}
}

The winner is Pilar Ackerman with 50.17% of the vote.

2008 Scripting Games - Advanced Event 2

 

Event 2: Skating on Thin Ice

$winners = cat C:\Scripts\skaters.txt | foreach {
  $item = $_.split(",")
  $scores = $item[1..7] | sort
  
  $skater = new-object psobject
  add-member -inp $skater noteproperty Name $item[0]
  add-member -inp $skater noteproperty HighestScore $scores[-1]
  add-member -inp $skater noteproperty LowestScore $scores[0]
  add-member -inp $skater noteproperty Score (($scores[1..($scores.length-2)] | measure-object -average).Average)
  add-member -inp $skater noteproperty Medal $null
  $skater
  
} | sort Score -desc | select -first 3

$winners[0].Medal="Gold medal"
$winners[1].Medal="Silver medal"
$winners[2].Medal="Bronze medal"

0..2 | foreach { "{0}: {1}, {2}" -f $winners[$_].Medal, $winners[$_].name ,$winners[$_].Score }

Results:
Gold medal: Guido Chuffart, 88.2
Silver medal: Jack Creasey, 85.8
Bronze medal: Cecilia Cornejo, 85.4
 

2008 Scripting Games - Advanced Event 1

 

Event 1: Could I Get Your Phone Number?

It seems I've taken the long path on this event, brute forcing every possible combination, padding all 3 letters to four chars.

 

$phone=Read-Host "Enter 7 digit phone number"
$letters = @{"2"="abc";"3"="def";"4"="ghi";"5"="jkl";"6"="mno";"7"="pqrs";"8"="tuv";"9"="wxyz"}
$numbers = 0..($phone.length-1) | foreach { "{0,-4}" -f $letters["$($phone[$_])"]}

"Computing word from Number, this may take a while..."

$wordList = cat c:\scripts\wordList.txt | where {($_.length -eq 7) -and ($_.substring(0,1) -match "[$($numbers[0])]")}


for($a=0; $a -lt 4; $a++){
    for($b=0; $b -lt 4; $b++){
        for($c=0; $c -lt 4; $c++){
            for($d=0; $d -lt 4; $d++){
                for($e=0; $e -lt 4; $e++){
                    for($f=0; $f -lt 4; $f++){
                        for($g=0; $g -lt 4; $g++){
                                    $word = "{0}{1}{2}{3}{4}{5}{6}" -f $numbers[0][$a],$numbers[1][$b],$numbers[2][$c],$numbers[3][$d],$numbers[4][$e],$numbers[5][$f],$numbers[6][$g]
                                    if($wordList -contains $word) {$wordList | where {$_ -eq $word}; return}
                        }
                    }
                }
            }
        }
    }
}

"Scan completed"

Thursday, February 14, 2008

Scripting Games 2008

The 2008 Scripting Games begins tomorrow. So, download the Games Competitors Pack and start coding :)

Wednesday, February 13, 2008

How to prevent DHCP lease to unauthorized computers

via Daniel Petri

The Windows DHCP Server Team has released a DHCP Server Callout DLL that helps administrators filter out DHCP Requests to a DHCP Server based on predefined list of MAC Addresses. The DLL checks if the requesting device MAC address is present in a known list of MAC addresses configured by administrators and performs one of two actions: Allow or Deny DHCP lease.

MAC address based filtering allows network administrators to ensure that only known set of devices in the system are able get IP address from DHCP Server. This DLL will help administrators to enforce additional security into the network. It is compatible with Windows 2003 and Windows 2008 Servers.

The usage is pretty simple and explained in the setup document along with the tool. Both the dll (MacFilterCallout.dll) and the Setup document (SetupDHCPMacFilter.rtf) are copied on to %SystemRoot%\system32 folder after installation. Download it here.

Monday, February 11, 2008

Tokenizing PowerShell scripts

 

In Windows PowerShell Virtual User Group Meeting #3, Lee Holmes presented a script that can do Syntax Highlighting in PowerShell in PowerShell v2 (CTP).

Reading through Lee's script, I remembered an older post where I was trying to take a script and resolve any aliases it contained to its full cmdlet name. Back then, Richard Siddaway commented that there is one major drawback. If you used the "%" sign as an alias for the Foreach-Object cmdlet then things may go wrong and behave unexpectedly, since "%" is also the symbol for modulo.

Well, not in the case of Tokenize. The Tokenize method takes the script content and breaks it apart into the script ingredients (PSTokenType). In the case of modulo, it knows when it's used as an alias and when it's used as an operator, amazing stuff!

 

To get a full list of all token types: 

PS > Get-EnumValues System.Management.Automation.PSTokenType

              Name Value
              ---- -----
           Unknown     0
           Command     1
  CommandParameter     2
   CommandArgument     3
            Number     4
            String     5
          Variable     6
            Member     7
         LoopLabel     8
         Attribute     9
              Type    10
          Operator    11
        GroupStart    12
          GroupEnd    13
           Keyword    14
           Comment    15
StatementSeparator    16
           NewLine    17
  LineContinuation    18
          Position    19

 

So, here we go... a sample script that contains aliases as well as the modulo sign:  

### demo.ps1 ###
dir | ? { ($_.length % 2) -eq 0 } | % { $_.name }

And here's a modified Convert-AliasToCmdlet script:
### Convert-AliasToCmdlet ###

param($file)

function Get-TokenType($token){
    switch($token.type){
        "Variable" {'${0}' -f $token.content}
        "Type" {"[{0}]" -f $token.content}
        "Command" { 
                $alias = (get-alias | where {$_.name -eq $token.content}).ResolvedCommandName
                if($alias) {$alias} else {$token.content}
        }
        default {$token.content}    
    }
}

$column=1
$content = [IO.File]::ReadAllText($file)
$tokens = [System.Management.Automation.PsParser]::Tokenize($content, [ref]$null)
$tokens | foreach {
    $padding=(" " * ($_.StartColumn - $column))
    $column=$_.EndColumn
    write-host ($padding + (Get-TokenType $_)) -NoNewline
}

write-host

 

The result shows that all aliases including the modulo sign were resolved as expected and each by its own usage context.

PS > Convert-AliasToCmdlet demo.ps1

Get-ChildItem | Where-Object {
 ($_.length % 2) -eq 0
} | ForEach-Object {
 $_.name
}

 

One thing I wasn't able to find is how to preserve TAB characters.

Thursday, February 7, 2008

Exchange Server Documentation Updates

 

The Exchange Server documentation team announced new and updated Exchange Server 2007 documentation content. You can read the full post here.

With regards to PowerShell, there are two updated help files for Exchange 2007 SP1:

 

Microsoft Exchange Server 2007 Service Pack 1 Help (19.9 MB)

This download contains a standalone version of Microsoft Exchange Server 2007 SP1 Help.
To view the most recent version of the Help file within Exchange Server, extract the Help file (exchhelp.chm) to the folder where the Help file is currently installed (typically C:\Program Files\Microsoft\Exchange Server\Bin).

 

Microsoft Exchange Server 2007 Service Pack 1 Shell Help (560 KB)

The Microsoft Exchange Server 2007 Exchange Management Shell Help file (Microsoft.Exchange.Management-Help.xml) helps you use cmdlets in the Exchange Management Shell to perform day-to-day administration of Exchange 2007. You can view help in the Exchange Management Shell by using the Get-Help cmdlet. Extract it to C:\Program Files\Microsoft\Exchange Server\Bin\<Lang>.

 

 

 

 

Sunday, February 3, 2008

Windows Messenger like popup

 

This script invokes a Windows Messenger like dialog, sending messages to the current user. You can call it in two ways:

 

Static

New-Popup.ps1 -message "hello world" -title "PowerShell Popup"

The form pops up at the right hand side, above the system tray and waits for the user to close it via the Close button. Script execution is halted until the form is closed.

static

 

 

With animation

New-Popup.ps1 -slide -message "hello world" -title "PowerShell Popup"

The dialog is sliding, no close button, it waits for a specified number of seconds based on the $wait parameter and then closes it self. Script execution is halted until the form is closed.

 

 slide

 

The script can be also downloaded here.

 

###################################
## New-Popup.ps1
##
## Messenger like popup dialog
##
## Usage:
## New-Popup.ps1
## New-Popup.ps1 -slide -message "hello world" -title "PowerShell Popup"


param(
    [int]$formWidth=200, 
    [int]$formHeight=110,
    [string]$title="Your title here",
    [string]$message="Your message here",
    [int]$wait=4,
    [switch]$slide
)

[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")

###############################
# extract powershell icon if doesn't exist
$icon = "$env:temp\posh.ico"
if( !(test-path -pathType leaf $icon)){
    [System.Drawing.Icon]::ExtractAssociatedIcon((get-process -id $pid).path).ToBitmap().Save($icon)
}

###############################
# Create the form
$form = new-object System.Windows.Forms.Form
$form.ClientSize = new-object System.Drawing.Size($formWidth,$formHeight)
$form.BackColor = [System.Drawing.Color]::LightBlue
$form.ControlBox = $false
$form.ShowInTaskbar = $false
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
$form.topMost=$true

# initial form position
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual

if($slide){
    $top = $screen.WorkingArea.height + $form.height
    $left = $screen.WorkingArea.width - $form.width
    $form.Location = new-object System.Drawing.Point($left,$top)
} else {
    $top = $screen.WorkingArea.height - $form.height
    $left = $screen.WorkingArea.width - $form.width
    $form.Location = new-object System.Drawing.Point($left,$top)
}

###############################
## pictureBox for icon 
$pictureBox = new-object System.Windows.Forms.PictureBox 
$pictureBox.Location = new-object System.Drawing.Point(2,2)
$pictureBox.Size = new-object System.Drawing.Size(20,20)
$pictureBox.TabStop = $false
$pictureBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::StretchImage
$pictureBox.Load($icon)


###############################
## create textbox to display the  message
$textbox = new-object System.Windows.Forms.TextBox
$textbox.Text = $message
$textbox.BackColor = $form.BackColor
$textbox.Location = new-object System.Drawing.Point(4,26)
$textbox.Multiline = $true
$textbox.TabStop = $false
$textbox.BorderStyle = [System.Windows.Forms.BorderStyle]::None
$textbox.Size = new-object System.Drawing.Size(192,77)
$textbox.Cursor = [System.Windows.Forms.Cursors]::Default
$textbox.HideSelection = $false


###############################
## Create 'Close' button, when clicked hide and dispose the form
$button = new-object system.windows.forms.button 
$button.Font = new-object System.Drawing.Font("Webdings",5)
$button.Location = new-object System.Drawing.Point(182,3)
$button.Size = new-object System.Drawing.Size(16,16)
$button.Text = [char]114
$button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$button.Add_Click({ $form.hide(); $form.dispose() })
if($slide) {$button.visible=$false}


###############################
## Create a label, for title text
$label = new-object System.Windows.Forms.Label
$label.Font = new-object System.Drawing.Font("Microsoft Sans Serif",8,[System.Drawing.FontStyle]::Bold)
$label.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$label.Text = $title
$label.Location = new-object System.Drawing.Point(24,3)
$label.Size = new-object System.Drawing.Size(174, 20)


###############################
## Create a timer to slide the form
$timer = new-object System.Windows.Forms.Timer
$timer.Enabled=$false
$timer.Interval=10
$timer.tag="up"
$timer.add_tick({

    if(!$slide){return}

    if($timer.tag -eq "up"){
        $timer.enabled=$true
        $form.top-=2
        if($form.top -le ($screen.WorkingArea.height - $form.height)){
            #$timer.enabled=$false
            $timer.tag="down"
            start-sleep $wait
        }
    } else {

        $form.top+=2
        if($form.top -eq ($screen.WorkingArea.height + $form.height)){
            $timer.enabled=$false
            $form.dispose()
        }
    }
})


## add form event handlers
$form.add_shown({
    $form.Activate()
    (new-Object System.Media.SoundPlayer "$env:windir\Media\notify.wav").play()
    $timer.enabled=$true
})

## draw seperator line 
$form.add_paint({    
    $gfx = $form.CreateGraphics()        
    $pen = new-object System.Drawing.Pen([System.Drawing.Color]::Black)
    $gfx.drawLine($pen,0,24,$form.width,24)      
    $pen.dispose()
    $gfx.dispose()
})


###############################
## add controls to the form
## hide close button if form is not sliding

if($slide){
    $form.Controls.AddRange(@($label,$textbox,$pictureBox))
} else {
    $form.Controls.AddRange(@($button,$label,$textbox,$pictureBox))
}

###############################
## show the form
[void]$form.showdialog()