Showing posts with label Script. Show all posts
Showing posts with label Script. Show all posts

Wednesday, October 06, 2010

Javascript Libraries and ASP.NET: A Guide to jQuery, AJAX and Microsoft

When Microsoft announced they would begin providing official support for jQuery, few of us realized how profoundly that announcement would eventually impact client-side development on the ASP.NET platform. Since that announcement, using jQuery with ASP.NET has moved from the obscure, to a central role in ASP.NET MVC’s client-side story, and now to the point of potentially superseding ASP.NET AJAX itself.

Get the full picture and read the rest at: http://visitmix.com/Articles/Javascript-Libraries-and-ASPNET

Saturday, July 10, 2010

Minimizing Performance Hit when Running Home Server Backup

I’ve always been annoyed by my home server backup. It always starts when I start the PC and then it eats a lot of resources. I often either postpone the backup or change its priority to low which more importantly reduces the IO priority to low as well.

But why not automate the latter?

And that is easy:

  • Start Event Viewer
  • Find HomeBackup event 768 in the Application log
  • Right-click specifying attach task to event
  • Follow the wizard, specify powershell.exe as program and ‘-noprofile c:\IdleBackupEngine.ps1’ as argument.
  • At the end of the wizard, select to open properties afterwards and change the task to use SYSTEM with highest privileges enabled
  • Finally, create c:\IdleBackupEngine.ps1. It only contains a single line: (gps backupengine).priorityclass="idle"

Have fun – working *during* the backup

Monday, June 14, 2010

Get-Line and Got-NewJob

Hi. It has been a while as I have been busy with my new job. I’m now working LEGO as Senior Solution Architect for www.lego.com. Visit a great web site and any feedback is welcome.

Consequently, I’m not using PowerShell as much as I used – to at least not for so complicated solution. On the other hand I’m using it almost daily as is it so useful for test web related things.

But my new colleagues know that I know PowerShell so today Michael asked about how to get a few lines from a text file. This is easy, but if it was easy, Michael would have figured it out himself. The problem is that the file is huge (E.G. 1.5GB), so using Get-Content with Select-Object or similar would be very memory intensive and thus slow. I said that I would either call .Net directly or embed some C# code in a script.

Well, now it is evening and I’m watching the Italian-Paraguay match and – hey – why not do a little blogging while and again!

As thought, so done. Here’s my solution. It took as little more than an hour including help and validation. For newcomers, use Get-Line –? for displaying the help nicely formatted.

The script -

<#
.Synopsis
Get line or lines from a text file
.Description
Get one of more lines from the specified file. Line numbers are positive and the first line is number 1.
.Inputs
Path
.Outputs
Array of strings
.Example
Get-Line $env:temp\lines.txt 23,897,45
Get lines 23, 45 and 897. Lines are returned in increasing order. E.g. line 23 is returned first, then line 45 and finally, line 897

#>
param(
[parameter(Mandatory=$true)]
[alias("file")]
[alias("fullname")]
[alias("name")]
[string]
[ValidateScript({Test-Path $_})]
# The path of the file to get the lines from
$path,
[Parameter(Mandatory=$true)]
[alias("numbers")]
[int64[]]
[ValidateScript({$_ -gt 0})]
# One or more line numbers (e.g. first line is 1) to retrieve from the file
$lines
)

add-type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.IO;

namespace Per
{
public class FileFunctions
{
public List<string> GetLines(string file, System.Collections.Generic.Queue<long> lines)
{
FileStream fs;
StreamReader sr;
List<string> linesFound = new List<string>();
using (fs = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 10 * 1024 * 1024))
{
using (sr = new System.IO.StreamReader(fs))
{
int lineCounter = 0;
int linesIndex = 0;
if (lines.Count == 0)
{
return new List<string>();
}
long findLine = lines.Dequeue();
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
lineCounter++;
if (lineCounter == findLine)
{
linesFound.Add(line);
linesIndex++;
if (lines.Count == 0)
{
break;
}
findLine = lines.Dequeue();

}

}
}
sr.Close();
}
fs.Close();

return linesFound;
}
}
}

'
@
$c=new-object Per.FileFunctions
[int64[]]$sortedLines=$lines | sort -unique | where {$_ -gt 0}
$c.GetLines((Resolve-Path $path),$sortedLines)





As always: Have fun!

Thursday, February 04, 2010

Invoking the PowerShell Debugger from Script

With PowerShell v2, a new and much improved command line debugger was introduced. The old one is still around though. Anyway, more information can be found in the help subject about_Debuggers.

The strange part is that Set-PsDebug –Step invokes the old debugger and there does not seem to be a way of invoking the new one. You can only invoke the new one by setting a breakpoint. Even though, breakpoints are a very useful feature which I use a lot, I would also like to do it from inside a script.

I have played around with some ways of doing this.

First, a self-contained function

Function Invoke-Debugger{
function debug{}
$bp=Set-PSBreakPoint -Command debug
debug
$bp | Remove-PSBreakpoint
}

function test{
write-host 1
Invoke-Debugger
write-host 2
}

test





It works great, but has the downside, that the current execution pointer is inside the Invoke-Debugger function -




1
Entering debug mode. Use h or ? for help.

Hit Command breakpoint on 'debug'

x.ps1:4 debug
7 $docs>>> l

1: Function Invoke-Debugger{
2: function debug{}
3: $bp=Set-PSBreakPoint -Command debug
4:* debug
5: $bp | Remove-PSBreakpoint
6: }
7:
8: function test{
9: write-host 1
10: Invoke-Debugger
11: write-host 2
12: }
13:
14: test





No matter how I try to tweak it, I end up in the same way.



Next, lets try using a two part approach (setting the breakpoint and doing some action to invoke it) -




$null=Set-PSBreakpoint -Variable InvokeDebugger

function test{
write-host 1
$InvokeDebugger=1
write-host 2
}

test



This is much better, now the execution pointer is right in the code -



1
Hit Variable breakpoint on '$InvokeDebugger' (Write access)

x.ps1:5 $InvokeDebugger=1
9 $docs>>> l

1: $null=Set-PSBreakpoint -Variable InvokeDebugger
2:
3: function test{
4: write-host 1
5:* $InvokeDebugger=1
6: write-host 2
7: }
8:
9: test
10:





Eventually, this led me to this piece of code. It is easier to write than the variable assignment and you can also define an easy-writeable alias for it -




function Invoke-Debugger{}
New-Alias id Invoke-Debugger
$null=Set-PSBreakPoint –Command Invoke-Debugger

function test{
write-host 1
id
write-host 2
}

test







The execution pointer is right at the call. If you include any statements in Invoke-Debugger, this will not work as well while ‘step’ will take execution into the function -















1
Hit Command breakpoint on 'Invoke-Debugger'

x.ps1:9 id
13 $docs>>> l

4: New-Alias id Invoke-Debugger
5: $null=Set-PSBreakPoint –Command Invoke-Debugger
6:
7: function test{
8: write-host 1
9:* id
10: write-host 2
11: }
12:
13: test
14:







This method also enables you to make conditional break using straight, normal code (compared to making the logic in the –action argument of Set-PSBreakPoint) -




filter test{
write-host "got $_"
if ($_ -eq 3) {id}
}

1..5 | test







and the output -




got 1
got 2
got 3
Hit Command breakpoint on 'Invoke-Debugger'

x.ps1:9 if ($_ -eq 3) {id}
14 $docs>>> l

4: New-Alias id Invoke-Debugger
5: $null=Set-PSBreakPoint –Command Invoke-Debugger
6:
7: filter test{
8: write-host "got $_"
9:* if ($_ -eq 3) {id}
10: }
11:
12: 1..5 | test
13:





You can include the Invoke-Debugger function and the Set-PSBreakPoint in your profile, so they are available in all our scripts.



Happy debugging..

Monday, January 18, 2010

Reference Variables

If you want to change a value inside a function, you have to use reference variables. They do not work the same way as they do in C, C# or VbScript.

This is a simple example -

function a([ref]$v) {
"a1 v=$($v.value)"
$v.value++
"a2 v=$($v.value)"
}

$x=0
"begin x=$x"
a ([ref]$x)
"end x=$x"







Note that must use [ref] when declaring the argument and also when calling. Note also that you must use ([ref]$x) in the call. Finally, you can see that [ref] actually converts the variable into a PSReference object and that you must use .Value to set/get the value.



But what if you want to transfer the reference variable across multiple function calls? Well, the logical approach is this -




function b([ref]$v) {
"b1 v=$($v.value)"
$v.value++
"b2 v=$($v.value)"
}

function a([ref]$v) {
"a1 v=$($v.value)"
$v.value+=10
b ([ref]$v)
"a2 v=$($v.value)"
}

$x=0
"begin x=$x"
a ([ref]$x)
"end x=$x"







The pattern is simply repeated. Just like you would in other languages. But THIS WILL NOT WORK. As the first call creates a PSReference object, using ([ref]$v) creates another level of redirection and things will fail. The correct solution is -




function b([ref]$v) {
"b1 v=$($v.value)"
$v.value++
"b2 v=$($v.value)"
}

function a([ref]$v) {
"a1 v=$($v.value)"
$v.value+=10
b $v
"a2 v=$($v.value)"
}

$x=0
"begin x=$x"
a ([ref]$x)
"end x=$x"





Note that in the second call, $v – a PSReference object – is simply transferred.



Bottom line: Avoid [ref] when you can. Return values or use scoped variables when possible. Remember that it is very easy to return multiple values from a function and assign them to different variables -




function c{
"Per"
"Denmark"
44
}
$Name,$Country,$ShoeSize=c



Thursday, July 30, 2009

A useful Test-Host Function

BS on Posh (tshell) have created a Test-Host function that is very useful. I have written some of these functions myself, but this beats the ones I have made, so I’ll switch :)

Test-Host supports normal ping but as ping is not always sufficient - may be blocked or you may have to wait for a service to be ready (who said RDP?) - TCP port is also supported. Finally, Test-Host accepts the server name as argument or from the pipeline (using V2 parameter binding) and it even allows you to specify a property, in case the server name is part of the object.

Test-Host sends the server argument down the pipeline, if that server is reachable. In this way, Test-Host acts more like a filter than a classic Test-Cmdlet. Maybe it could have been called Where-HostReachable or similar?

Another comment I have, is that the server argument should have been called ComputerName (to follow other Cmdlets). To make the function more flexible, alternative names like server and name could have been specified with [Alias()]. A quick fix here is to add ComputerName and Name as aliases: [Alias("ComputerName","Name")] Place the text before the $server parameter.

I also found a bug in the code. You have to remove the [string] before the $server to be able to use $property. If server is forced into a string, the properties are lost and thus cannot be selected.

Some examples of it use -

Ping test (not reachable)
PS> test-host www.dr.dk

TCP port 80 test (reachable)
PS> test-host www.dr.dk -tcp 80
www.dr.dk

Ping test (reachable)
PS> test-host www.jp.dk
www.jp.dk

Ping test (not reachable)
PS> test-host 1.2.3.4

TCP port 80 test (reachable)
PS> test-host 1.2.3.4 -tcp 80

Pipe names
PS> "www.dr.dk","www.jp.dk" | test-host -tcp 80 | foreach {"Web server runs on $_"}
Web server runs on www.dr.dk
Web server runs on www.jp.dk

Pipe names, one not reachable
PS> "www.dr.dk","www.jp.dk","nosuchserver" | test-host -tcp 80 | foreach {"Web server runs on $_"}
Web server runs on www.dr.dk
Web server runs on www.jp.dk

Build objects array with two servers
PS> $objs=@()
PS> $obj="" | select Server,Role; $obj.server="www.dr.dk"; $obj.role="webserver"; $objs+=$obj
PS> $obj="" | select Server,Role; $obj.server="nosuchserver"; $obj.role="mailserver"; $objs+=$obj
PS> $objs

Server Role
------ ----
www.dr.dk webserver
nosuchserver mailserver

Test, selecting the Server property as the name
PS> $objs | test-host -tcp 80 -property Server | foreach {"Server $_ is running"}
Server @{Server=www.dr.dk; Role=webserver} is running

Test, constructing a ComputerName property to show the alias parameter binding
PS> $objs | select @{n="Computername";e={$_.server}} | test-host -tcp 80 -property ComputerName| foreach {"Server $_ is running"}
Server @{Computername=www.dr.dk} is running







Wednesday, March 25, 2009

Get-OpenFile

One of the many nice things about PowerShell it that you can use the existing utilities you’ve got and you love. And then you can enrich them using PowerShell. You do this best by converting the output to objects – which you then can use down the pipeline.

One such example is here. Handle.exe (sysinternals.com) returns open files (and other handles). If you wrap that with a script, you can convert the output to objects and then manipulation is so easy.

Here is Get-Openfile.ps1

param($fileFilter=$(throw "Filter must be specified"))

handle $fileFilter | foreach{
if ($_ -match '^(?<program>\S*)\s*pid: (?<pid>\d*)\s*(?<handle>[\da-z]*):\s*(?<file>(\\\\)|([a-z]:).*)') {
$matches | select @{n="Path";e={$_.file}},@{n="Handle";e={$_.handle}},@{n="Pid";e={$_.pid}},@{n="Program";e={$_.program}}
}
}





Handle must be found via $env:path or an alias. Note the use of –match and $matches and how a regex is used to match the output and easily pick up the relevant parts of the output using names. One advice: Learn regex!



An example




PS C:\Windows\PolicyDefinitions> get-openfile  er.exe

Path Handle Pid Program
---- ------ --- -------
C:\Windows\System32\en-US\... 38 7004 SearchIndexer.exe
C:\Windows\en-US\explorer.... 38 5764 explorer.exe
C:\Windows\SysWOW64\en-US\... 50 8484 explorer.exe



 



I originally called the Path property for File, but to bind the value automatically (in the pipeline), I renamed it to path.



This is nice -




PS> get-openfile  er.exe | dir

Directory: C:\Windows\System32\en-US

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 27-05-2008 09:17 7680 SearchIndexer.exe.mui

Directory: C:\Windows\en-US

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 02-11-2006 16:12 27136 explorer.exe.mui

Directory: C:\Windows\SysWOW64\en-US

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 02-11-2006 16:12 36864 explorer.exe.mui



 


 



And this is just as nice -




PS> get-openfile  er.exe | gps

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
1554 33 178276 107420 622 863,72 7004 SearchIndexer
957 87 74504 82528 356 465,01 5764 explorer
513 32 28768 34044 203 28,27 8484 explorer



 



I use the method in Get-OpenFile a lot as that provides many advantages.



 







PS. You must be administrator to run handle. Get-OpenFile simply does not return any objects if you are not an admin. Handle.exe does not set $? or $lastexitcode, so checking for the condition is not easy.



PS2. Handle.exe can also display the user name and close handles (Remove-Openfile?). It is left to the reader to implement that. Handle.exe does not display files opened across the network. –a can do that, but the path is a strange \Device\Mup path, so it is excluded.

Monday, September 29, 2008

Restarting Windows XP from a startup script is not easy

Yes, they are still out there running the big businesses!

Today, I was messing with a startup script, that should restart the PC - but WMI did not do it and shutdown.exe just returned device is not ready. This should have been fixed in SP2 - at least some KBs claims that - but that is clearly not the case.

Eventually, I found a way around: Create an AT-job to do the restart. That worked. But it was too slow as AT-jobs needs to be postponed at least a minute.

Finally, good old Sysinternals came to the rescue - this time in the shape of psexec. Sysinternals have saved the day many times and back in the late nineties you could simply not lock down and figure out where to relax security on an NT 4 without regmon and filemon. Anyway, back to today. This was how I solved my present problem -

psexec -accepteula -sd shutdown -r -t 5



 



-sd tells it to start the process in system context (the s) and detached (d) as I did not want to wait for the command - I wanted my script to finish as long as it had time to do so.

Saturday, September 27, 2008

Backing up remote eventlogs using WMIC

This week, I was out on an oil rig in the North Sea helping out in some fail-over testing. After the test, I had to collect eventlogs from all the computers involved and as I'm an scripting guy, I definitely did not want to do that manually. Now, this is a Windows Server 2003 and Windows XP environment. Furthermore, it is a highly restricted environment so I could not install PowerShell or plug in and use my own PC; I had to look elsewhere. First, I looked at Sysinternals's psloglist, but that could not do the job. It could either dump the eventlogs as text - I wanted binary - or it could dump as binary and empty the logs at the same time. I only wanted a backup. Next, I googled and live searched, but did not really find anything useful, when my old fried WMIC resurfaced in my mind. Wasn't there some method call in WMI, that could do the job?

Using my own PC, I could easily find the method. It was just a matter of writing gwmi win32_nt, pressing tab (as I naturally use PowerTab), selecting win32_NTEventlogFile and piping it into Get-Member -

gwmi win32_nteventlogfile | gm



And the method BackupEventlog showed up. I started wbemtest on a Windows XP PC and checked that the method also existed on Windows XP. After this, I started to figure out the WMIC command line - which quite often is hard to get right. I ended up with -



 




wmic /node:"server" nteventlog where "logfilename='system'" call backupeventlog "c:\system.evt"



Note, that the backup file specification is local to the remote node. I tried saving it back on the local PC with \\currentpc, but got access denied and I did not want to create a share for this. Also note that the WMIC alias for Win32_NTEventlogfile is NTEVENTLOG



Finally, I ended up with this backup.bat file. All.txt contains a list of the computers, I needed to get the logs from. Note that I delete the evt-files first as backupeventlog will not overwrite an existing file (if I needed to re-run the script)




set targetdir=%temp%\logs
for /f %%I in (all.txt) do del \\%%I\c$\*.evt
for /f %%I in (all.txt) do wmic /NODE:"%%I" nteventlog where "logfilename='system'" call backupeventlog "c:\system.evt"
for /f %%I in (all.txt) do wmic /NODE:"%%I" nteventlog where "logfilename='security'" call backupeventlog "c:\security.evt"
for /f %%I in (all.txt) do wmic /NODE:"%%I" nteventlog where "logfilename='application'" call backupeventlog "c:\application.evt"
for /f %%I in (all.txt) do robocopy \\%%I\c$ %targetdir%\%%I *.evt /z /njs /njh
for /f %%I in (all.txt) do del \\%%I\c$\*.evt

Tuesday, June 24, 2008

How do ISA figure out which authentication to use?

So you have published your Exchange server and your are using forms based authentication (FBA). But when you use Outlook Anywhere or ActiveSync (MSRPC), it bypasses FBA. Why does it do that and how does that work?

Well, thanks to my coworker Claus-Ole Olsen, I got the question answered. ISA uses the User-Agent header/string to decide whether it will actually use FBA or not! You can also select different forms based on the value - for different device capabilities.

The ISA GUI tells you it uses FBA, but you just cannot trust that as the User-Agent header will modify the rule!

Read it all here on TechNet Microsoft Internet Security and Acceleration Server 2006 Managing User-Agent Mappings, including scripts for viewing and setting the values.

If you want to avoid the VBScript, you can use PowerShell. This is Get-IsaUserAgentMapping.ps1 -

 

param([switch]$pretty)
$root=new-object -com fpc.root
$isaArray=$root.GetContainingArray()
$mappings=$isaarray.ruleelements.UserAgentMappings |
select PersistentName,UserAgent,Description,Enabled,@{n="FBAFormsType";e={
# For values, see http://technet.microsoft.com/en-us/library/bb794715.aspx
switch ($_.FBAFormsType) { 0 {"HTML 4.01"} 1 {"cHTML"} 2 {"XHTML-MP"} 3 {"Basic"} }
}},order
if ($pretty.isPresent) {
$mappings | Sort Order | Format-Table -auto UserAgent,Description,Enabled,FBAFormsType,Order
}
else {
$mappings
}

 


Adding and modifying is left as an exercise for yourself ;)

Wednesday, June 11, 2008

Creating More Efficient Microsoft Active Directory-Enabled Applications

I just found this MSDN article about optimizing your queries. Besides good advice on how to create optimal queries, you can also instruct Active Directory to log expensive queries and even control the threshold value of when a query is expensive!

Furthermore, the ANR search is explained e.g. how you can search for 'Sam' when you do not know whether it is a name, a SAM account name etc. Just like the GUI search in Users and Computers.

 

And while you are at it, I can also recommend reading How Active Directory Searches Work.

Thursday, May 22, 2008

Is this PowerShell Session a 32-bit or a 64-bit?

How can one identify whether the current PowerShell process is running the 32-bit version (x86) or the 64-bit version of PowerShell? Well first, why would you care? You are right, normally I do not care, but if I need to execute VBscript with Invoke-Script, I need to know as that only works on 32-bit Windows.

I'm now running Vista x64 - and that is actually a pleasure. For the first time, I feel my PC is responsive enough when running many applications. If you are interested, my PC is a Dell D830 with 4 GB RAM and a Intel Turbo Channel module having 1 GB. OK specs and nice performance.

One of the minor problems I have encountered is that MSScriptControl.ScriptControl used by Invoke-Script cannot be started from 64-bit PowerShell. I simply does not exist :(

So I set off hunting a way of finding out how to differentiate, so I could do something clever.

First, I looked at the $host variable, but that seemed to return the same info. Next, I looked at the current process. The only difference I found was the image path, there does not seem to be any flag indicating the execution mode. Strange, I would have expected that.

Before jumping on a image path test, I looked as wmi32_process as well. This did not really help me, but at least win32_operatingSystem.OSArchitecture help me figuring out the platform.

This all added up in Get-Architecture.ps1 which contains -



param([switch]$CurrentProcess)

if ($CurrentProcess.ispresent) {
$me=[diagnostics.process]::GetCurrentProcess()
if ($me.path -match '\\syswow64\\') {
32
}
else {
64
}
}
else {
$os=Get-WMIObject win32_operatingsystem
if ($os.OSArchitecture -eq "64-bit") {
64
}
else {
32
}
}


Example -



PS> powershell -noprofile {get-architecture -currentprocess}
64
PS> C:\WINDOWS\syswow64\windowspowershell\v1.0\powershell.exe -noprofile {get-architecture -currentprocess}
32
PS> powershell -noprofile {get-architecture}
64
PS> C:\WINDOWS\syswow64\windowspowershell\v1.0\powershell.exe -noprofile {get-architecture}
64
PS> powershell -noprofile {get-architecture -currentprocess}
64
PS> C:\WINDOWS\syswow64\windowspowershell\v1.0\powershell.exe -noprofile {get-architecture -currentprocess}
32


Suggestions for improvements are highly welcome!

Monday, March 03, 2008

2008 Scripting Games, Solution 10

# Advanced Event 10: Blackjack!
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent10.mspx

# -ShowValue is an aid in testing the script
param([switch]$showValues,$dealerColor=$host.ui.RawUI.ForegroundColor,$playerColor=$host.ui.RawUI.ForegroundColor)

# Define cards and their values, prepare for double-value cards (the Ace), but I did not implement the
# logic for playing with it
$cards="Hearts","Diamonds","Spades","Clubs" | % {
$suite=$_
"Ace,11,1","Two,2","Three,3","Four,4","Five,5","Six,6","Seven,7","Eight,8","Nine,9","Ten,10","Jack,10","Queen,10","King,10" | % {
$card="" | select Name,Rank,Suite,Value,Value2
$Card.Rank,$Card.Value,$Card.Value2=$_.split(",")
$card.Name=$Card.Rank + " of $suite"
$card.Suite=$suite
$card
}
}


# Shuffle (using sort-random) and place in stack - love the ability to use .net :D

[Collections.Stack] $cards=$cards | sort {(new-object random).next()}

# Show a hand
function ShowCards([switch]$dealer) {
begin {
""
if ($dealer.ispresent) {
$color=$dealerColor
write-host -foreground $color "Dealer's cards:"
}
else {
$color=$playerColor
write-host -foreground $color "Your cards:"
}
$sum=0
}
process {
if ($showValues.ispresent) {
write-host -foreground $color ("{0} ({1})" -f $_.name,$_.value)
}
else {
write-host -foreground $color $_.name
}
$sum+=$_.value
}
end {
if ($showValues.ispresent) {
write-host -foreground $color ("Total {0}" -f $sum)
}
}
}

# Stay or hit function
function Ask{
write-host -nonewline "Stay (s) or hit (h) ?"
do {
$key=$host.ui.rawui.ReadKey("noecho,includekeydown").character
} until ($key -eq "s" -or $key -eq "h")
write-host $key
$key
}

# Sum up the hand
function value{
begin {
$sum=0
}
process {
$sum+=$_.value
}
end {
$sum
}
}

$player=@()
$dealer=@()

# Start dealing
$player+=$cards.pop()
$player+=$cards.pop()
$player | ShowCards
$playervalue=$player | value

# And the dealer
$dealer+=$cards.pop()
$dealer | ShowCards -dealer
# Second card is hidden
$dealer+=$cards.pop()
$dealervalue=$dealer | value

# Player's turn

# Continue dealing until gt 21 or 'stay'
while ((ask) -eq "h") {
$player+=$cards.pop()
$player | ShowCards
$playervalue=$player | value
if ($playervalue -ge 21) {
break
}
}

"You have $playervalue"
""
$dealer | ShowCards -dealer
""

if ($playervalue -gt 21) {
"Over 21. Sorry you loose"
}
elseif ($playervalue -eq 21) {
"Black Jack. You win"
}
elseif ($dealervalue -ge $playervalue) {
"Dealer has $dealervalue. Sorry you loose"
}
else {
# Dealer playes, must continue until player is defeated, black jack or exceeds 21
while ($dealervalue -lt $playervalue) {
$dealer+=$cards.pop()
$dealer | ShowCards -dealer
$dealervalue=$dealer | value
}
if ($dealervalue -gt 21) {
"Dealer has over 21. You win"
}
elseif ($dealervalue -eq 21) {
"Dealer has Black Jack. Sorry you loose"
}
elseif ($dealervalue -ge $playervalue) {
"Dealer has $dealervalue. Sorry you loose"
}
else {
"Dealer has $dealervalue. You win"
}
}

2008 Scripting Games, Solution 9

# Advanced Event 9: You're Twisting My Words
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent9.mspx

# Load text from file
$text=type c:\scripts\alice.txt
# Split into words (space delimited)
# Convert each word to a character array, reverse the array and construct a string again
$all=$text.split(" ") | % { $w=$_.tochararray(); [array]::reverse($w); [string]::join("",$w) }
# Finally, convert all words a line of text using the default $OFS delimiter (which is space)
"$all"

2008 Scripting Games, Solution 8

# Advanced Event 8: Making Beautiful Music
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent8.mspx

param($minminutes=75,$maxminutes=80,$artistMax=2)

if ($minminutes -ge $maxminutes) {
throw "Min minutes must be smaller than max minutes"
}
# If only this file had header rows!
# Load the csv, convert each line to an object and
# convert the duration to timespans
$songs=type c:\scripts\songlist.csv | % {
$obj="" | select Artist,Song,Duration
# Multi assignment - each variable gets a value assigned
$obj.Artist,$obj.Song,$duration=$_.split(",")
$min,$sec=$duration.split(":")
$obj.Duration=new-timespan -min $min -sec $sec
$obj
}
# Randomizer
$random=new-object random

do {
$listOk=$false
write-verbose "Generating list.."
# Randomize the list
$songs=$songs | sort {$random.next()}

# Pick only $artistMax numbers per artist
$songs=$songs | group Artist | % {
$_.group[0..($random.next($artistMax))] | % { $_ }
}

# See if we can build a list
$burnSongs=@()
$burnDuration=New-Timespan
$brokeOut=$false
foreach($song in $songs) {
$burnSongs+=$song
# += does not work on timespan objects
$burnDuration=$burnDuration+$song.Duration
if ($burnDuration.totalMinutes -ge $minminutes -and `
$burnDuration.totalMinutes -le $maxminutes) {
# Is with-in constraints, exit
$listOk=$true
$brokeOut=$true
break
}
elseif ($burnDuration.TotalMinutes -gt $maxminutes) {
$brokeOut=$true
break # No need to spent more time on this list
}
}
if (!$brokeOut) {
throw "Songlist does not contain material that can fullfill the constraints"
}
} until ($listOk)
# Output duration as m:ss - the default hh:mm:ss does not seem to meet the requirements
$burnSongs | Format-Table Artist,Song,@{Label="Duration"; `
Expression={"{0}:{1:00}" -f $_.Duration.Minutes,$_.Duration.Seconds}}
# And again m:ss format
"Total music time {0}:{1}" -f [math]::truncate($burnDuration.TotalMinutes),$burnDuration.Seconds

2008 Scripting Games, Solution 7

# Advanced Event 7: Play Ball!
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent7.mspx

# Teams - as character array
$teams="ABCDEF".toChararray()
# Create combinations, use $c to start each iteration one step futher in the array
$plays=$teams | % {$c=0} { $team=$_; $c++; $teams[$c..$teams.length] | % { "$team vs. $_" } }
# Randomize - the easy way
# $plays | sort {(new-object random).next()}

# Randomize - and make sure the plan is more realistic by adding two additional
# constraints: Make sure -
# - no team plays more than 3 matches in a row and
# - a team is not left idle for 40% of a tournement
[int] $idle=[math]::truncate($plays.count*0.4)

do{
$AllTeamsOk=$true
# Randomize / shuffle deck
$plays=$plays | sort {(new-object random).next()}

# Use foreach statement as break is used
foreach($team in $teams) {
# Build a list of Y/.'s for each game a use a regex to look for consecutive matches
$teamplays=$plays | % {$g=""} { if ($_ -match $team) { $g+="Y" } else {$g+="."} } {$g}
write-debug "$team $teamplays"
if ($teamplays -match "y{3}") {
write-verbose "bad $team plays 3 matches in a row - reshuffle"
$AllTeamsOk=$false
break
}
elseif ($teamplays -match "\.{$idle}") {
write-verbose "bad $team is too much idle in the tournement - reshuffle"
$AllTeamsOk=$false
break
}
}
}
until ($AllTeamsOk)
$plays

Thursday, February 28, 2008

Controlling Diskpart from PowerShell

The other day, I got a question from one who had attended my PowerShell training classes: How do I control diskpart from a scripting language? Well, PowerShell can do it, but only because it is layered on top of .Net. And this is what I really like about PowerShell; do you do easily grow out of it.

 

The script below starts diskpart, redirects stdin/stdout and writes/reads to them. The tricky part is starting the process correctly and reading from the subprocess without stalling. The StandardOutput stream does not support timed reads, so if you read one time too many, the process freezes.

 

# ProcessStartInfo - to control stdin/stdout
$si=new-object diagnostics.processstartinfo
$si.filename="$env:windir\system32\diskpart.exe"
$si.RedirectStandardInput=$true
$si.RedirectStandardOutput=$true
$Si.UseShellExecute=$false

# Start process
$p=[diagnostics.process]::start($si)


# The difficult part - reading without stalling - and knowing when to stop
function show($maxwait=60){
# Buffer for matching the prompt
$read=""
$start=Get-Date
do {
# Touch EndOfStream to let peek see next bytes
# Do not know why, but it works
$p.StandardOutput.EndOfStream > $null
# If there is a byte pending
while ($p.StandardOutput.peek() -ne -1) {
# Get the byte as a char
$char=[char]$p.StandardOutput.read()
# Write it to the host
write-host -nonewline $char
# And save it in a buffer
$read+=$char
}
# Exit when maxwait has exceeded or the prompt at the end of the buffer
} until ($read -match "DISKPART\>\s+$" -or ((Get-Date)-$Start).TotalSeconds -gt $maxwait)
write-host
}

do {
show
$command=read-host "Command"
$p.StandardInput.Writeline($command)
} until($command -eq "exit")
# Get remaining output, no prompt at exit, so just timeout
show -maxwait 3

 


The method can also be used with other similar tools, just remember that if you do not have to keep any context, it is much easier to call the tool multiple times with different arguments. In PowerShell you can always capture the output using a simple variable -


sc $env:temp\x.x "list disk" -encoding ascii
$output=diskpart /s $env:temp\x.x

or


$ipconfig=ipconfig

 


Have fun!

2008 Scripting Games, Solution 6

# Advanced Event 6: Prime Time
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent6.mspx

# By default stop at 200
param($max=200)
2 # by definition
# Main loop, step by two as only odd numbers can be primes
for($i=3;$i -le $max; $i+=2) {
# Init prime boolean
$prime=$true
# Walk through divisors up to the number at hand
for($d=3; $d -lt $i; $d+=2) {
write-verbose "$i $d $($i % $d)"
if (!($i % $d)) {
# Divisor found, break for and look at next value
$prime=$false
break
}
}
#
if ($prime) {$i}
}

2008 Scripting Games, Solution 5

#Advanced Event 5: You Call That a Strong Password?
#http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent5.mspx

param($password)

# Start score
$score=13
# Load wordlist
$wordlist=type c:\scripts\wordlist.txt

# Tests
$len=$password.length

if ($wordlist -contains $password) {"Is a word";$score--}
if ($wordlist -contains $password.substring(0,($len-1))) {"When the last character is removed, it is a word";$score--}
if ($wordlist -contains $password.substring(1)) {"When the first character is removed, it is a word";$score--}
if ($password[0..$len] -contains "0" -and $wordlist -contains ($password -replace "0","o")) {"Is a word, where the letter 'o' is replaced with zero";$score--}
if ($password[0..$len] -contains "1" -and $wordlist -contains ($password -replace "1","l")) {"Is a word, where the letter 'l' is replaced with digit '1'";$score--}
if (!($len -ge 10 -and $len -le 20)) {"Is shorter than 10 or longer than 20";$score--}
# Find regex character classes at http://msdn2.microsoft.com/en-us/library/20bw873z.aspx
# Class d - digit
if ($password -notmatch "\d") {"Does not contain a digit";$score--}
# Class Ll - Letter, Lowercase
if ($password -cnotmatch "\p{Ll}") {"Does not contain a lowercase letter";$score--}
# Class Lu - Letter, Uppercase
if ($password -cnotmatch "\p{Lu}") {"Does not contain an uppercase letter";$score--}
# Class S - symbol, P - punctuation
if ($password -cnotmatch "\p{S}|\p{P}") {"Does not contain any symbols or punctuation";$score--}
if ($password -cmatch "\p{Ll}{4}") {"Contain more than 4 consecutive lowercase letters";$score--}
if ($password -cmatch "\p{Lu}{4}") {"Contain more than 4 consecutive uppercase letters";$score--}
if (($password[0..$len] | % {$c=0} {$c++; $password[$c..$len] -ccontains $_}) -contains $true) {
"A duplicate letter (case-insensitive) is found"
$score--
}

""
if ($score -le 6) {
"A password score of {0} indicates a weak password" -f $score
}
elseif ($score -le 10) {
"A password score of {0} indicates a moderately-strong password" -f $score
}
else {
"A password score of {0} indicates a strong password" -f $score
}

Sunday, February 24, 2008

2008 Scripting Games, Solution 4

# Advanced Event 4: Image is Everything
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent4.mspx

# Being non-native English speaking, I wanted to turn this
# into a generic calendar, that could be used anywhere
# Consequently, I created a function that by default uses
# the default culture and then specified en-US when calling
# it to comply with event rules

function Show-Calendar {
param($startDate=(Get-Date),$Culture)

if ($Culture) {
# Use supplied culture
$culture=[globalization.cultureinfo] $Culture
}
else {
# Use default culture
$culture=Get-Culture
}

# Get first day of week, the calender must be show correct
# e.g. in US Sunday is the first day of week, in Denmark Monday is the first
$firstDayOfWeek=$culture.DateTimeFormat.FirstDayofWeek
# Get the weekdays
$weekdays=$culture.DateTimeFormat.ShortestDayNames
# Calculate the width or the weekdays to right-adjust values
$width=($weekdays | measure-object -maximum length).maximum

# If first day of week is not 0, shift the order
if ([int]$FirstDayOfWeek) {
$ShiftedWeekdays=$weekdays[$FirstDayOfWeek..($WeekDays.count)] + $weekdays[0..($FirstDayOfWeek-1)]
}
else {
$ShiftedWeekdays=$weekdays
}

$StartDate=[datetime]$startDate
# Make sure we start the first day in the month
$StartDate=New-Object datetime $StartDate.year,$StartDate.month,1

# Working date
$Date=$StartDate

# Header
$Date.ToString($culture.DateTimeFormat.YearMonthPattern,$culture)

# Process scriptblock and pipe output to Format-Table
&{
while ($date.Month -eq $StartDate.Month) {
# Week-break code
if ([int]$date.DayOfWeek -eq $firstDayOfWeek -and $week) {
$week
$week=$null
}
# Create week object with days as properties
if (!$week) {
$week=New-Object Object
$ShiftedWeekdays | % {
Add-Member -inputObject $week NoteProperty $_ $null
}
}
# Get the day e.g. the property name
$dayOfWeek=$Weekdays[$date.DayOfWeek]
# Assign value to property
$week.$dayOfWeek="{0,$width}" -f $date.day # Right adjust values
# Values must be right-aligned as the days will $null-values will
# set off Format-Table left-aligning those columns

# Next day
$date=$date.AddDays(1)
}
if ($week) {
# Print out final week, if any
$week
}
} | Format-Table -autosize # Pick up the alias properties and format it

}

# Prompt user
$month=read-host "Enter month in month/year format"
# Split into month and year
$month,$year=$month.split("/")
# And call function with en-us culture
Show-Calendar (New-Object Datetime $year,$month,1) "en-us"