Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts

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!

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



Monday, November 16, 2009

PowerShell 2.0 *is* supported for Exchange 2007 SP2

In case you wondered, the greatly improved PowerShell 2.0 can be installed on servers running Exchange 2007. But you must be running Service Pack 2.

BTW: You cannot find PowerShell v2 on www.microsoft.com/downloads. You have to go to http://support.microsoft.com/kb/968929.

Monday, November 02, 2009

DFS, IPv6 and – sort of – disabling it

Ask the Directory Services Team has a good article on troubleshooting DFS links (DFS Referrals and IPv6: Outta site!) as well as a discussion of how not to  disable IPv6 (unbinding it from an adapter) and how to do it correctly (KB929852).

In case you really need to disable IPv6, consider using a Group Policy Preference or automate it with PowerShell -

Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents 0xffffffff -type dword





Useful information.

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, May 20, 2009

Live Mesh Logs

If you are using Live Mesh, you may want to dig deeper into what it is really doing. To do so, I just found some interesting log files.

Mesh has a trace/activity log placed in C:\users\*\appdata\local\microsoft\live mesh\gacbase\Moe-*-*-*-YYYY-MM-DD-*.log. It seems to log quite a bit of information, so expect huge log files.

The file is a kind of CSV-file, but without headers. Luckily, Import-Csv in PowerShell V2 has a –header arguments, so you can decode the file using -

dir *.log | import-csv -header Date,Severity,Component,Number1,Number2,Number3,Message







As you can see, I have no clue as to what numbers 1 to 3 are for, but please comment, if you know.

Thursday, April 30, 2009

Get-Packet, a Packet Sniffer for PowerShell

This guy, Robbie Foust, wrote just that – I’m deeply impressed. Read more/get it here.

PS. Remember that Netmon 3.3 was recently released. Get that here.

Wednesday, April 15, 2009

Making PowerTab 0.99 Beta 2 work on PowerShell V2 (CTP3)

Being very fond of PowerTab, it has been a loss that it did not work for PowerShell V2. But it turns out to be very simple to fix the problem. It fails as the parameter signature for $host.ui.rawui.NewBufferCellArray seems to have changed in V2.

Fixing the problem is a matter of changing two lines in <PowerTabDir>\TabExpansion.ps1. The old line is commented out -

#      $message = $host.ui.rawui.NewBufferCellArray(@('[Tab]'),'Yellow','Blue')
$message = $host.ui.rawui.NewBufferCellArray(@('[Tab]'),[consolecolor]::Yellow,[consolecolor]::blue)





and




#      $message = $host.ui.rawui.NewBufferCellArray(@('[Err]'),'Yellow','Red')
$message = $host.ui.rawui.NewBufferCellArray(@('[Tab]'),[consolecolor]::Yellow,[consolecolor]::red)




Have fun!

 



PS: It does not work in ISE

Tuesday, April 07, 2009

PowerShell, Getting the Value Out of a ScriptBlock

When you are doing a little advanced scripting and passing scriptblocks around, one of the problems I encountered was how to execute the scriptblock in a pipeline and get the value out of it. Say you want the user to be able to enter a scriptblock and compare your data to the value returned from it – how is that done?

Executing a scriptblock is easy using the &-operator

$sb={"Hello World!"}
&$sb





 



You can also use the invoke method -




$sb.invoke()





If you want to have a filtering scriptblock e.g. comparing a value with a scriptblock (not its value), I found it easy and logical to use where(-object) -




$sb={$_.name –like ‘note*’}
dir $env:windir | where $sb
# or
if (dir $env:windir | where $sb) {"Slipped through filter, go on"}





But what if you want to execute it, feeding in values from the pipeline to get new values? (Often you would only send in one value at a time). The answer is foreach(-object) -




$sb={$_.name.toupper()}
dir $env:windir note* | foreach $sb





When can this be useful? In numerous places, I would say. What if you have some getter script, with these arguments -




Get-XXX –dateproperty <property>
–value <value|scriptblock>
–filter <scriptblock>





 


If this case the code is something like (pseudo code) -




get-object | where &$filter| foreach {
if ($value –is [scriptblock]) {
$compareto=$_ | foreach $value
else {
$compareto=$value
}
if ($_.$dateproperty –gt $value) {
“ok”
}
}



 


The user can then either stick it a value or calculate a value using the scriptblock – and that based on the data from the object itself and before the comparison is made -



# Getting files accessed after January 1st
get-xxx–dateproperty lastAccessTime –value ([datetime] "2009-01-01")
# Getting files accessed 90 days after it was modified
get-xxx–dateproperty lastaccesstime –value {$_.lastwritetime.adddays(90)}





The latter example was not possible without parsing a scriptblock.

Saturday, March 28, 2009

Free PowerShell eBooks

Just read/browsed two free PowerShell eBooks and here are my comments -

  • Frank Koch, Two books, An introduction for people with no real background knowledge and Administrative tasks. Available in German and English (only read the English version – even though I do read German). Worth a read – especially if you are new or still trying to get it. The latter covers a lot of the free add-ons you can get. The German shines through in some places in the English version, but I do not think that is too big a problem.
  • Keith Hill, Effective Windows PowerShell, 50 pages. You should read this if you have some knowledge of PowerShell. It covers the most important topics and goes quite deep into output formatters and parsing modes. Even longtime users may learn a few tricks. This books was updated March 2009 with some PowerShell v2 information.

Happy reading!

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.

Strange Select-Object Behavior in PowerShell V2 CTP3

In PowerShell v1, the rules are as follows -

  • Select-Object used with –first or –last do not change the object type of the objects passing through
  • Select-Object selecting properties or adding changes the object type to PSCustomObject

Proof -

PS> gps -id $pid | Get-Type # Normal
System.Diagnostics.Process
PS> gps -id $pid | select name,ws | Get-Type # PSCustomObject
System.Management.Automation.PSCustomObject
PS> gps -id $pid | select name,ws | select | Get-Type # PSCustomObject
System.Management.Automation.PSCustomObject
PS> gps -id $pid | select * | Get-Type # Normal (errors are left out)
System.Management.Automation.PSCustomObject
PS> gps -id $pid | select | Get-Type # Normal
System.Diagnostics.Process



 



In PowerShell V2 CTP3 the rules seem to be -




  • Select-Object used with –first or –last works as in V1


  • Select-Object, selecting properties or adding properties, creates a new type called Selected.<originaltype>


  • Select-Object resulting in a Selected.<type> will be turned into a Selected.System.Management.Automation.PSCustomObject! Very strange and very problematic in real-life use



Examples, showing it -




PS> gps -id $pid | Get-Type # normal
System.Diagnostics.Process
PS> gps -id $pid | Select name,ws | Get-Type # Selected.<type>
Selected.System.Diagnostics.Process
PS> gps -id $pid | Select name,ws | Select | Get-Type # Selected...PSCustomObject
Selected.System.Management.Automation.PSCustomObject
PS> gps -id $pid | select | Get-Type # normal
System.Diagnostics.Process
PS> gps -id $pid | select | select | Get-Type # normal
System.Diagnostics.Process
PS> gps -id $pid | select -f 1| select | Get-Type # normal
System.Diagnostics.Process
PS>



 



PS. Get-Type is not a built-in, but this function -




function Get-Type($inputObject) {
process { if ($_ -is [object]) {$_.PSTypeNames[0] } }
end { if ($inputObject -is [object]) { $inputObject.PSTypeNames[0] } }
}

PowerShell V2 CTP3 – Compatibility Problem - $MyInvocation

In PowerShell v1, you can use $MyInvocation.invocationname to test whether a script was dot-sourced or not.

This works in V1

if ($myinvocation.invocationname -ne ".") {
throw "Script should be dot-sourced to make this work"
}



but it not work in V2, and I cannot find a work-around :(




PS> '"invocation=" + $myinvocation.invocationname' | sc $env:temp\test.ps1
PS> &$env:temp\test.ps1
invocation=C:\Users\po\AppData\Local\Temp\test.ps1
PS> . $env:temp\test.ps1
invocation=C:\Users\po\AppData\Local\Temp\test.ps1
PS> # does it work when an alias is used?
PS> new-alias test $env:temp\test.ps1
PS> test
invocation=test
PS> . test
invocation=test



 



Update: Posted the problem on connect. MS has already closed my report with a 'fixed', so let's hope that is the case.

Wednesday, March 18, 2009

Jeffrey Snover - Distinguished Engineer

Jeffrey, the father of PowerShell, got appointed Distinguished Engineer - well deserved. Read more here.

Friday, December 12, 2008

Matching multi-line text and converting it into objects

Sometimes your input is simply text and you have to work with it. If it is line-based e.g. each object is a separate line, it is not so hard, but what if the output is very dynamic and the information you need to combine spans multiple lines? In this case, you could start making some kind of state machine e.g. when the starting point is reached, set a variable, save data as you go and when the ending point or the next starting point is reached, construct the object and emit it to the pipeline so it can be used.

Well, there are better ways and in this example, I'll combine these things to make is much more straight-forward and generic -

  • Use regular expressions (regex) for matching the information you want
  • Show how to construct a regex in a readable way spanning multiple lines and containing comments
  • Using a dynamic approach, where the named captures in the regex are automatically converted into properties on the output object. This means that you only have to specify the name one, the loop processing the matches is totally generic and can be reused.

The examples uses output from repadmin /showrepl, but this is just an example I picked up more or less randomly. The idea here is to show the use of regex and converting the result to objects, the idea is not to create a bullet-proof parser of the output from repadmin /showrepl.

Here's the example -

# Define some text, in this case the text is stored in $text, but
# is could come from anywhere

$text=@'
DC Options: IS_GC
Site Options: (none)
DC object GUID: aff429b7-5694-4bda-ae4a-daa7d371ce0f
DC invocationID: b578e349-5846-46c5-8e01-b4a81d609e27
==== INBOUND NEIGHBORS ======================================
DC=company,DC=org
BLL\045ADDC001 via RPC
DC object GUID: 26446473-3433-4c73-942d-c750f0e476ec
Last attempt @ 2007-08-21 13:38:53 was successful.
CN=Configuration,DC=company,DC=org
BLL\045ADDC001 via RPC
DC object GUID: 26446473-3433-4c73-942d-c750f0e476ec
Last attempt @ 2007-08-21 13:38:53 was successful.
CN=Schema,CN=Configuration,DC=company,DC=org
BLL\045ADDC001 via RPC
DC object GUID: 26446473-3433-4c73-942d-c750f0e476ec
Last attempt @ 2007-08-21 13:38:54 was successful.
DC=DomainDnsZones,DC=company,DC=org
BLL\045ADDC001 via RPC
DC object GUID: 26446473-3433-4c73-942d-c750f0e476ec
Last attempt @ 2007-08-21 13:38:54 was successful.
DC=ForestDnsZones,DC=company,DC=org
BLL\045ADDC001 via RPC
DC object GUID: 26446473-3433-4c73-942d-c750f0e476ec
Last attempt @ 2007-08-21 13:38:54 was successful.
'
@

$regex=[regex] "(?msx)
# Option m = multi-line e.g. ^=start of line and $=end of line
# Option s = single-line e.g. . includes end-of-line
# Option x = spaces and comments are allowed in the pattern making this
# line possible

# Start of line (^), match partition, eat until end of line ($)
^ (?<partition> (CN|DC)=[^`$]+? )`$

# any chars - ? means lazy i.e. match as few characters as possible
.+?

# match site before \ using a series of wordchar
(?<Site> \w+) \\

# match domain controller afterwards
(?<DC> \w+)

# any chars
.+?

# match the date last attempted, note the spaces are escaped as option x is used
Last\ attempt\D+ (?<date> [\d\-]+\ [\d\:\.apm]+ )
"


# Search for pattern matches in $text
$regex.matches($text) | Foreach-Object {
# Save current pipeline object, so it is available from inside the next foreach-object
$match=$_
# Construct a new, empty object. Always return objects as output whenever possible. It
# makes using the output must easier
$obj=new-object object
# Get all the group names defined in the pattern - ignore the numeric, auto ones
$regex.GetGroupNames() | Where-Object {$_ -notmatch '^\d+$'} | Foreach-Object {
# And add each match as a property. When multiple results are returned, the
# value must be picked up using an index number hence the GroupNumberFromName call
add-member -inputobject $obj NoteProperty $_ $match.groups[$regex.GroupNumberFromName($_)].value
}
# emit the object to the pipeline
$obj

}



This is the output -




partition                     Site                          DC                            date
--------- ---- -- ----
DC=company,DC=org... BLL 045ADDC001 2007-08-21 13:38:53
CN=Configuration,DC=compan... BLL 045ADDC001 2007-08-21 13:38:53
CN=Schema,CN=Configuration... BLL 045ADDC001 2007-08-21 13:38:54
DC=DomainDnsZones,DC=compa... BLL 045ADDC001 2007-08-21 13:38:54
DC=ForestDnsZones,DC=compa... BLL 045ADDC001 2007-08-21 13:38:54



A final note: If you input is an array of strings, you have to convert it into a single text string before calling $regex.matches. This can easily be done, by joining the elements using newline as a delimiter -




[string]::join("`n",$array)

Wednesday, November 05, 2008

Server 2008 R2 Active Directory and PowerShell

Filling my head at Tech-Ed, luckily, others are more efficient bloggers than I am.

Read this excellent summary by Dmitry of the "Windows Server 2008 R2 Active Directory: What’s Coming Up"” session at IT Forum (TechEd EMEA IT Pro) by Robert DeLuca and Alain Lissoir

Tuesday, November 04, 2008

A Day at Tech-Ed the PowerShell Way

Start-Service $body

Set-Location $bathroom
Clear-Item $body

Set-Location $restaurant
Get-Item $breakfast | Format-table | Add-Content $body

Set-Location $bathRoom
$body.oldFood | Out-Null
Clear-ItemProperty $body teeth

Set-Location $desk
$today=(Get-Date).Date
$potentialSessions=Get-ChildItem $sessions | `
Where {$_.description -gt $interest -and $_.date -eq $today} | foreach {
$_ | Add-Member Noteproperty Priority (read-host "Priority") -Passthru
} | where {$_.priority}
$plannedSessions=$potentialSessions | group timeslot | foreach {
$_.group | sort priority | select -first 1
}

Set-Location $conferenceCenter
$informationOverload=$false
$plannedSessions | foreach {
if ($informationOverload) {
Start-Sleep -Minutes 90
$informationOverload=$false
}
else {
Invoke-Item $_ | where {$_.content -notlike "sales pitch"} | `
Add-Content -path $body.brain
}
[void] (Start-Sleep -Minutes 30) -and (Add-Content $body $coffee)
if ($body.brain.overloaded) {$informationOverload=$true}
}

Set-Location ($anywhere | where {$_.beerCount -gt 5 -and $_.price -eq $null} | select -first 1)
while ($body.thirsty -and $body.alcoholPercentage -lt 1.5) {
Add-Content $body $beer
}

Set-Location $hotelRoom
Start-Sleep -minutes (60*7)

# See you tomorrow ;)


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

Friday, August 15, 2008

PowerShell Support in SQL 2008

Was browsing the new features of SQL 2008. There are quite a lot and some interesting ones as well. One is the support of PowerShell. There are both a PS provider ("The provider lets you navigate the hierarchy of SQL Server objects using a path similar to a file system path. Each object is associated with a class from the SQL Server Management object models. You can use the methods and properties of the class to perform work on the objects. For example, if you cd to a databases object in a path, you can use the methods and properties of the Microsoft.SqlServer.Managment.SMO.Database class to manage the database.") and the ability to execute PowerShell commands from within the management application ("The SQL Server 2008 version of SQL Server Management Studio supports starting PowerShell sessions from the Object Explorer tree. The SQL Server 2008 version of SQL Server Agent supports PowerShell job steps"). Read it all here.