Archive

Posts Tagged ‘powershell’

PowerShell Log function with word wrapped output to the screen

February 16th, 2012 No comments

I like to have logging in most scripts I write, and this is the current incarnation of my logging function. It logs to c:\location\of\script.ps1.log (or wherever/whatever your script is named). It uses write-host to output a word wrapped version of the text to the screen. It reads in the window size to determine the best place to cut up the sentence.

# Name of the currently executing script
$ScriptName = $MyInvocation.MyCommand.Path 
# Simple log file name, OurScriptName.ps1.log
$LogFile = $ScriptName + ".log"
 
# Word wrap function, return word wrapped version of passed string
function WordWrapStr($str)
{
	# Holds the final version of $str with newlines
	$strWithNewLines = ""
	# current line, never contains more than screen width
	$curLine = ""
	# Loop over the words and write a line out just short of window size
	foreach ($word in $str.Split(" "))
	{
		# Lets see if adding a word makes our string longer then window width
		$checkLinePlusWord = $curLine + " " + $word
		if ($checkLinePlusWord.length -gt (get-host).ui.rawui.windowsize.width)
		{
			# With the new word we've gone over width
			# append newline before we append new word
			$strWithNewLines += [Environment]::Newline
			# Reset current line
			$curLine = ""
		}
		# Append word to current line and final str
		$curLine += $word + " "
		$strWithNewLines += $word + " "
	}
	# return our word wrapped string
	return $strWithNewLines
}
 
# Log function, append passed string to log file
function Log([string]$logline)
{
    $timestamp = Get-Date -f "yyyy-MM-dd HH:mm:ss"
    $StdLogLine = "$timestamp> $logline"
	# Add the log line to our log file in ascii with timestamp
    Add-Content $LogFile $StdLogLine -encoding ascii
    # Print to the screen the word wrapped version (no timestamp)
	write-host (WordWrapStr $logline)
}
 
Log "This text will first be appended to $LogFile, ascii encoded.  Also cut up the text and print it out word wrapped to the screen.  It works even with really long lines."

Example with and without word wrapped write-host:

Categories: Uncategorized Tags:

Batch file bootstrap PowerShell script for easy double click running

February 16th, 2012 No comments

The scenario is you have a PowerShell script that takes no paramaters and prompts the user for all the information it needs. How do you make it extra simple for people to check it out of source control and run it?

You have 1 .bat or .cmd file in the root of the folder. Create a subfolder like internal and put all your ps1 files in there. Here is what I put together so you could just double click the .bat/.cmd file and have your PowerShell script execute without worry.

@echo off
setlocal
REM Check to see if Powershell is in path
for %%X in (powershell.exe) do (set FOUND=%%~$PATH:X)
if not defined FOUND (
	echo Please install PowerShell v2 or newer.
	pause
	exit /b 1
)

REM Check to see if we have powershell v2 or later
PowerShell.exe -command "& { if ($host.Version.Major -lt 2) { exit 1 } }"
if %ERRORLEVEL% EQU 1 (
	echo Please upgrade to PowerShell v2 or later.
	pause
	exit /b 1
)

REM Set scriptDir to directory the batch file is located
set scriptDir=%~dp0
echo Running powershell.exe %scriptDir%internal\MyScript.ps1
REM Note the RemoteSigned in case the system is defaulted to restricted
powershell.exe -ExecutionPolicy RemoteSigned -command "& { "%scriptDir%internal\MyScript.ps1"; exit $LastExitCode }"
if "%ERRORLEVEL%" NEQ "0" (
	echo There may have been a problem with MyScript.  Please read the above text carefully before exiting.
)
pause
endlocal

After replacing MyScript with the name of your script, stick the contents in MyScript.cmd in your main directory. Make sure you have your powershell script in a subdirectory called internal. When the user double clicks on the file we check if PowerShell is in the path and that it is v2 or greater. We abort if either of those conditions are false. Finally we execute the PowerShell script with paramaters to ensure it runs on a new system without having the execution policy set yet. If you exit your powershell script with a non-zero the batch file will remind the user to read carefully before the window closes.

Categories: Uncategorized Tags: ,

[SOLVED] svn user commits _svn folder !?!?!?

February 8th, 2012 No comments

Names and paths changed to protect the innocent :)

Here’s a quick one that wreaked a moment of havoc. A user had comitted _svn folders to the repository. The build server uses _svn instead of .svn by way of an environment variable SVN_ASP_DOT_NET_HACK. This caused the continuous integration server to stop updating since it freaked out trying to pull down a _svn folder over a _svn folder.

This is what it looked like in the log:

PS J:\> svn log --verbose -r 1234 https://svnserver/svn/WidgetX/trunk
------------------------------------------------------------------------
r1234 | johnsmith | 2012-02-08 11:14:09 -0800 (Wed, 08 Feb 2012) | 1 line
Changed paths:
   A /trunk/src/newthing
   A /trunk/src/newthing/0.jpg
   A /trunk/src/newthing/1.jpg
   A /trunk/src/newthing/2.jpg
   A /trunk/src/newthing/9.jpg
   A /trunk/src/newthing/_svn
   A /trunk/src/newthing/_svn/prop-base
   A /trunk/src/newthing/_svn/props
   A /trunk/src/newthing/_svn/text-base
   A /trunk/src/newthing/_svn/tmp
   A /trunk/src/newthing/_svn/tmp/prop-base
   A /trunk/src/newthing/_svn/tmp/props
   A /trunk/src/newthing/_svn/tmp/text-base
Just another commit
------------------------------------------------------------------------

To fix a user comitting _svn folders:

# My environment variable was set so checkouts use _svn instead of .svn (legacy reasons)
# Unset it so we get .svn folders as normal
$env:SVN_ASP_DOT_NET_HACK=$null
# Checkout the branch so we get a working copy with .svn instead of _svn as usual
svn co https://svnserver/svn/WidgetX/trunk
# Round up the offending _svn folders and delete them!
get-childitem -recurse -force | where { $_.name -eq "_svn" } | foreach {  svn delete ""$_.fullname"" }
svn commit -m "Cleaning up _svn folders" trunk

I tried to reproduce this scenario using .svn folders, but svn smartly told me it was a reserved name. Adding _svn it didn’t complain at all (see bug). There might be some way to force a .svn folder in, in which case you can use this procedure except set the SVN_ASP_DOT_NET_HACK variable to any value instead of unsetting it. Also change $_.name -eq “_svn” to $_.name -eq “.svn”.

Categories: Uncategorized Tags: ,

Powershell: Invoke-Expression and Tee-Object

January 29th, 2009 2 comments

So in my previous post I wrote about how I got around a problem with some standard error from Perforce. I want to take my work around function and pipe it into Tee-object so I can simultaneously display and log what’s going on with the execution of my consistency check. A consistency check could take a while, so being able to see it’s running output is important.

In my function, I use Invoke-Expression. Well, turns out it’s not so easy to just pipe that into Tee-Object. At least in PSH V1.

Here is my test PowerShell script:

write-host Running in PowerShell Version $host.version.major
write-host 'Executing: Invoke-Expression "print.exe /?" | tee-object -filepath test.log'
Invoke-Expression "print.exe /?" | tee-object -filepath test.log
write-host Now lets get the content of test.log
get-content test.log
write-host
write-host 'Executing: Invoke-Expression "print.exe /?" > test.log'
Invoke-Expression "print.exe /?" > test.log
write-host Now lets get the content of test.log 
get-content test.log

Run it inside PowerShell v1:

PS C:\temp> .\test.ps1
Running in PowerShell Version 1
Executing: Invoke-Expression "print.exe /?" | tee-object -filepath test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
Now lets get the content of test.log
Get-Content : Cannot find path 'C:\temp\test.log' because it does not exist.
At C:\temp\test.ps1:5 char:12
+ get-content  <<<< test.log
 
Executing: Invoke-Expression "print.exe /?" > test.log
Now lets get the content of test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.

The pipe doesn’t work, but standard redirection does. I also tried piping it to out-file. Oddly, the output goes straight to the screen and nothing to the file.

Now let’s try it in v2 CTP3:

PS C:\temp> .\test.ps1
Running in PowerShell Version 2
Executing: Invoke-Expression "print.exe /?" | tee-object -filepath test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
Now lets get the content of test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
 
Executing: Invoke-Expression "print.exe /?" > test.log
Now lets get the content of test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
PS C:\temp>

In v2, we get the expected result.

I was about to give up on this, but I found this post that describes basically the same issue. The suggested fix is to wrap the Invoke-Expression (or iex) in parenthesis.

PS C:\temp> (Invoke-Expression "print.exe /?") | tee-object -filepath test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
PS C:\temp> get-content test.log
Prints a text file.
 
PRINT [/D:device] [[drive:][path]filename[...]]
 
   /D:device   Specifies a print device.
 
PS C:\temp>

Keep on truckin..

Categories: Uncategorized Tags:

Weird Powershell Standard Error Behavior

January 28th, 2009 No comments

Ok I’m baffled. I am working on the previously mentioned powershell perforce backup script. I intentionally corrupted one of my database files to make sure I’m correctly catching the error. This ran me into a brick wall with trying to trap some standard error output from Perforce. At least I think it’s standard error. At this point I could be convinced some new output stream has mysteriously been created that redirects output to some black hole.

Setting the script aside, let’s take a look at the basics. Here’s what happens in a standard dos command window:

F:\p4backup>p4d -r F:\P4ROOT -xv > test.log 2>&1
 
F:\p4backup>type test.log
Validating db.counters
Validating db.logger
Validating db.user
Validating db.group
Validating db.depot
Validating db.domain
Validating db.view
Validating db.review
Perforce server error:
        Database open error on db.have!
        BTree is corrupt!
 
F:\p4backup>

Now let’s try it in powershell:

PS F:\p4backup> p4d -r F:\P4ROOT -xv > test.log 2>&1
PS F:\p4backup> type test.log
Validating db.counters
Validating db.logger
Validating db.user
Validating db.group
Validating db.depot
Validating db.domain
Validating db.view
Validating db.review
PS F:\p4backup>

No error! It mysteriously disappeared. Ok this lead me to believe something funky was up with standard error redirection, so I wrote a little standard error test program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace StandardErrorTester
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("This is being written to standard output.");
            Console.Error.WriteLine("This is being written to standard error.");
        }
    }
}

Let’s run the test program in a dos command window:

F:\p4backup>StandardErrorTester.exe > error.log 2>&1
 
F:\p4backup>type error.log
This is being written to standard output.
This is being written to standard error.
 
F:\p4backup>

Works as expected. Now let’s try it in powershell:

PS F:\p4backup> .\StandardErrorTester.exe > error.log 2>&1
PS F:\p4backup> type error.log
This is being written to standard output.
StandardErrorTester.exe : This is being written to standard error.
At line:1 char:26
+ .\StandardErrorTester.exe  <<<< > error.log 2>&1
PS F:\p4backup>

The standard error does get wrapped with some strange powershell stuff, but all the output is present. I’m wondering if in the perforce case, the errors are getting interpreted and hidden somehow. Very strange.

Here is simple work around. It’s a function that takes what you want to execute, executes it via a batch script which makes the context the dos command shell. You can still check $LASTEXITCODE and filter the function through pipes or redirection. It’s ugly because it has to create a stub batch file, but it gets around the problem I’m having.

function RunCombineStdErrStdOut
{
    param([string]$commandLineToRun)
    # Get the path where the current script is so we can create
    # our batch file next to it
    $curScript = $script:myInvocation.MyCommand.Path
    $curPath = (Split-Path $curScript)
    # We're going to create a batch file right next to our ps1 script, wherever it lives.
    $runCombBatchFile = $curPath + "\RunCombineStdErrStdOut.cmd"
    if (test-Path -path $runCombBatchFile)
    {
        # Remove the batch file if it exists before we start 
        remove-Item $runCombBatchFile
    }
    # Create our tiny batch script to combine standard error and standard out
    write-Output '@echo off' | out-file $runCombBatchFile -enc ascii -append
    write-Output 'REM This script is auto-created by a PowerShell script' | out-file $runCombBatchFile -enc ascii -append
    write-Output 'REM Modifications will be lost. Do not edit directly' | out-file $runCombBatchFile -enc ascii -append
    write-Output ('REM Script Created by: ' + $curScript.ToString()) | out-file $runCombBatchFile -enc ascii -append
    # %* is a little trick that refers to all the paramters, not just a single one
    # Remove comment for debugging
    #write-Output 'echo Running %*' | out-file $runCombBatchFile -enc ascii -append
    write-Output '%* 2>&1' | out-file $runCombBatchFile -enc ascii -append
    # This will execute the batch file with the command passed to the function
    Invoke-Expression ($runCombBatchFile + " " + $commandLineToRun)
    # You can still check $LASTEXITCODE once this executes
    # it will reflect whatever command we executed
}
 
 
#  Just a simple example to get you going
RunCombineStdErrStdOut "ipconfig /all"
Categories: Uncategorized Tags:

PowerShell V1 doesn’t like to exit assignment functions

January 27th, 2009 1 comment

I was working on a fancy backup script for Perforce written in Powershell and I’ve definitely come across a bug in PowerShell V1.  Take a look at this:

function Usage()
{
	write-host "Hey fool, you need to pass 1 paramter to this utility!"
	exit 1
}
 
if ($args.Count -ne 1)
{
	Usage
}
write-host "Doing stuff..."

So that little snippet, if run without a parameter, will tell you you’re a fool and never tell you it’s doing stuff. I use exit to abort the entire script.  Works as expected.  Now take a look at this:

function GetMyWidget()
{
	# Pretend I couldnt find my widget
	if ($foundWidget -ne $true)
	{
		write-host "Ooops, something went wrong and I want to exit this script!"
		exit 1
	}
	return $widget
}
$w = GetMyWidget
write-Host "Whatever you do, don't print this!"

I expect the above snippet to print out Ooops… and that’s it. Unfortunately this is what it prints:

PS H:\temp> H:\temp\test.ps1
Ooops, something went wrong and I want to exit this script!
The ‘=’ operator failed: System error..
At H:\temp\test.ps1:11 char:5
+ $w =  <<<< GetMyWidget

Whatever you do, don’t print this!
PS H:\temp>

Execute the same code in Powershell V2 CTP3, no error, and it NEVER prints “Whatever you do, don’t print this!”.   The difference between the snippets is I’m assigning a variable to the return value of my function that contains an exit.  Note both environments $errorActionPreference is continue.

That is pretty annoying.  The workaround is to put $script:errorActionPreference = “Stop” before you exit statement, or to make sure that is the preference somewhere.

Categories: Uncategorized Tags: