Welcome to MSDN Blogs Sign in | Join | Help

In a recent case I was involved with I stumbled upon something quite interesting. The customer had set up a brand new Windows 2008 Server running IIS, and made it into a Read Only Domain Controller (RODC). Having done this they now wanted to add the FTP-server Role to the existing IIS installation and found that they couldn't.

The reason for this is that the RODC is quite picky about what server roles and features it will allow you to install. I recommend looking at the following articles, but they still don't cover everything.

Applications That Are Known to Work with RODCs
http://technet.microsoft.com/en-us/library/cc732790.aspx

Planning for Application Compatibility with RODCs
http://technet.microsoft.com/en-us/library/cc731746.aspx

The main lesson to be learned is that although the application is listed as known to work with RODCs that doesn't necessarily mean it installs well once the server has been promoted to RODC. When it comes to FTP you have two valid options to get this working:

  1. Install the FTP server before promoting the server to RODC
  2. Install the FTP Service 7.5 for IIS7. This is a stand-alone installer and works just fine.

/ Johan

I've finally had the time to sit down and create a simple game for my Zune. Rather than running off and creating a complex game requiring a lot of AI, etc I wanted to create a simple application so that I could focus on the XNA-specifics. I decided to make a Code Breaker game, also known as Master Mind. It's a pretty straight-forward game. There is no increasing difficulty curve and no fancy AI to be written, so it seemed like an excellent first attempt.

Prerequisites

Before I could start I needed to install the necessary software. I already had Visual Studio 2008 and the Zune Software. Actually you don't need the full Visual Studio 2008. You can use the completely free Visual C# 2008 Express Edition instead.

You do need to sign up as a creator in the XNA Creators Club. Having done that, download the XNA Game Studio and install it. Now you're set to go.

Creating content

I gathered a few sound effects and whipped up some graphics for my project. I decided that this is what I wanted the game to look like:

The background would be customizable. I wanted to be able to cycle through the available pictures on your device.

The architecture

I decided on a fairly simple architecture for my game. In my main class (Game1.cs) I deal with the necessities. E.g. I load the sound effects that will be used throughout the application, and I create the GraphicsDeviceManager that will be used to draw to the screen. Apart from that I have two classes that I use. MainGame and MainMenu. As their names imply they either show a menu or the game screen. User input is handled in the main class and it then calls methods in the relevant classes. E.g. if the menu is shown and the user clicks on the Zune "squircle" then a menu item has been selected and the event should bubble to the MainMenu class and not to the MainGame class.

I do more or less the same thing in the Draw-method of the main class. Depending on what is shown on screen individual methods of the two classes are called. And the screen is updated accordingly.

Zune features

I made sure to add at least some of the Zune-specific features.

Pictures

Like I've already mentioned I added support for getting backgrounds from the pictures on the device.

Music

I also let the player choose a playlist to be running in the background. (Anything already playing when the game is started will continue to play until the player actually changes this setting) I could have given full access to each individual song, but building an interface for that would have been quite ridiculous. Playlists only seemed like a good idea.

Touch interface

After some usability testing I found that in order to get this game working properly I needed to decide if I should incorporate the touch/sweep interface of the Zune 2.0 devices, or if I should settle for "proper" clicks only. I decided on the latter. Mainly because I still needed to rely on the center click, and it was easy to accidentally brush away to the side when trying to click. It also ment that this application would work with all Zunes.

Other design choices

In the beginning I let the player change colors by clicking up/down and going to the next "peg" by clicking right/left.

This turned out to be a bit complicated. It was quite easy to accidentally click up or down as well which would mean that you not only moved to the next peg, but also changed the previous pegs color. I ended up using the following key settings.

  • Click Right - Move Right
  • Click Left - Move Left
  • Center Click - Change color / Click button
  • Play / Pause - Change color "back"
  • Back - Menu

Installing

If you want to install and run this little game you need to go through the motions filed under Prerequisites above. Open the project in Visual Studio 2008 and dock your Zune. You should then be able to deploy the release version or debug the application directly on the device.

Download

You can download the project here

Additional screenshots

Here are some additional screenshots of the game:

 

I hope you enjoy it!

/ Johan

Today we release Internet Explorer 8. You'll be able to download it later today from http://www.microsoft.se/ie8

/ Johan

Stephen Elop, the president of Microsoft's Business Division recently held a presentation where he allowed the viewers to glimpse 10 years into the future. It's amazing to look at a "real" interpretation of the future interfaces and technologies rather than the standard hollywood stuff. - You know the holographic projections that for some strange reason still use slow-typing green text on a black background. The video is really inspirational, and what I really liked was how Stephen, after showing the video, went through all the technologies show in the clip and discussed how they could be accomplished.

If you're in a rush you can skip the first part of the presentation and jump to directly to the video at about 14½ minutes. But I really recommend watching the whole thing, especially the final rundown where he discusses the underlying technologies.

/ Johan

It's been fun to see the reception that PowerShell has gotten. People are using it for tons of different stuff and the ingenuity in some of the scenarios is quite impressive. There is one thing, however, that you should be aware of. PowerShell is not an ideal candidate for execution through the Task Scheduler.

Problem

A long-running PowerShell script is being executed using the Task Scheduler. At apparently random intervals the script will fail with a System.Management.Automation.PipelineStoppedException.

Cause

Looking up the exception on MSDN might not shed any light on the situation unless you know what is happening.

The exception thrown when a cmdlet or a Windows PowerShell provider attempts to write to the pipeline or perform a number of other Windows PowerShell operations after the pipeline has been terminated. The pipeline could have been terminated before the call was made or during the call.

When the Task Scheduler executes a PowerShell script it needs to use the .NET Framework. When running the .NET Framework it uses the console session (Session 0). This problem occurs when someone is logged on to the root console (either physically or using Terminal Services) and logs off while the script is executing.

Repro

You can easily reproduce this by creating a small script that sleeps for a while. Schedule the script to run immediately and then log off. To illustrate I've used the following script:

function Main()
{
     Start-Transcript "C:\Test\Psjob.txt"
     'Start-Sleep'
     Start-Sleep (2*60)
     'Ready'
     Stop-Transcript
}
 
Main

When correctly executed this script will generate a transcript that looks like this:

**********************
Windows PowerShell Transcript Start
Start time: 20090305111600
Username  : DOMAIN\SYSTEM
Machine   : MACHINENAME (Microsoft Windows NT 5.2.3790 Service Pack 2)
**********************
Transcript started, output file is C:\Test\Psjob.txt
Start-Sleep
Ready
**********************
Windows PowerShell Transcript End
End time: 20090305111800
**********************

However, if I log off the console while the script is executing I get the following transcript:

**********************
Windows PowerShell Transcript Start
Start time: 20090305114300
Username  : DOMAIN\SYSTEM
Machine   : MACHINENAME (Microsoft Windows NT 5.2.3790 Service Pack 2)
**********************
Transcript started, output file is C:\Test\Psjob.txt
Start-Sleep
**********************
Windows PowerShell Transcript End
End time: 20090305114340
**********************

As you can see the transcript is properly ended as PowerShell shuts down, but if you look at the output in the middle you'll see that we never write "Ready" to the file. This is because the script was prematurely terminated.

Resolution

The quick and easy resolution is to upgrade to Vista, Windows Server 2008 or later, since as of Windows Vista the console session is no longer running in Session 0.

Perhaps you've read my earlier post on advanced feature delegation using PowerShell and the IIS 7.0 PowerShell Provider. This could actually be called Part II.

The previous post was based on a quick question from a premier customer. He is a never-ending source of intriguing questions and I always enjoy his "just one thing"-type of inquiries. They often force you to think a little extra. Anyway, the other day he came with a question, which (as always) seemed simple enough at a first glance. The goal was to set Feature Delegation for SMTP E-mail And Forms Authentication to "Read Only".

Now, most basic configuration topics have been covered in countless posts, and at first I thought that this shouldn't be so hard. There is, however, a major difference. The SMTP E-mail and Forms Authentication settings are set in the root web.config. Not in applicationhost.config.

If we open up and compare web.config before, and after we change the setting, we'll notice that the change made by the IIS Manager is adding the following into the root <configuration>-block:

<location path="" overrideMode="Deny">
    <system.net>
        <mailSettings>
            <smtp>
                <network />
                <specifiedPickupDirectory />
            </smtp>
        </mailSettings>
    </system.net>
    <system.web>
        <authentication>
            <forms>
                <credentials />
            </forms>
            <passport />
        </authentication>
    </system.web>
</location>

Okay, so using what we already know from the old post we should put together a call that looks something like this:

Set-WebConfiguration "/System.Net/mailSettings/smtp"  -value @{<something... overrideMode="Deny", maybe?>} -PSPath IIS:\

Please note that this is NOT the correct syntax. It is close, but no cigar. What we need to do is:

  1. Correctly set the overrideMode. The above sample is not the way to do it.
  2. Make sure the changes are saved to web.config

So, the final, (and correct,) call to the Set-WebConfiguration cmdlet would look like this:

Set-WebConfiguration //System.Net/mailSettings/smtp -metadata overrideMode -value Deny -PSPath MACHINE/WEBROOT

The Forms Authentication call would look like this:

Set-WebConfiguration //System.Web/authentication -metadata overrideMode -value Deny -PSPath MACHINE/WEBROOT

I've searched quite extensively for samples on this and so far I've found none. So I thought someone should write it down. :)

 

Happy Holidays! / Johan

A very common scenario that keeps sprouting new heads like a hydra is Office Automation. Let me start by saying that this is not supported.

There is a KB-article number 257757 discusses this, and clearly states the following:

Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, ASP.NET, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when Office is run in this environment.

Why is this not supported?

The crucial thing to consider is the fact that Microsoft Office is designed to be an end-user, single client product. Automating Microsoft Office in a client application, using the identity and security context of the logged on client is supported, but unattended execution is not.

There are a number of things that can go wrong and article 257757 lists most of them. For arguments sake, let's consider the following scenario:

Your server application starts up and uses CreateObject to create an instance of MS Word. Word tries to read the settings of the current client and since your application is using the Network Service account this presents an immediate problem. There are, for example, certain methods that rely on a default printer being installed. All this can lead to serious problems. You then need to monitor the server, since Office might show a modal dialogue for some reason. The "install on first use" feature of MSI might also kick in, prompting the client to install additional features. All this would hang the current thread. Also, running one instance of Word, might be okay, but what happens when you get 100 more or less concurrent requests and each request starts up an instance of Word? MS Word is not a server, it's an excellent piece of single-client software.

So what should I do instead?

Actually, most of your problems can be resolved using HTML. I've seen applications where lines and lines of complex code were used for generating an Excel document, when it could just as well have been made using a standard HTML-table. Excel would have no problems whatsoever reading a table like the one below:

<table>
    <tr>
        <td><b>Person</b></td>
        <td><b>Age</b></td>
    </tr>
    <tr>
        <td>Pete</td>
        <td>30</td>
    </tr>
    <tr>
        <td>Claire</td>
        <td>40</td>
    </tr>
    <tr>
        <td>Average age</td>
        <td>=AVERAGE(B2:B3)</td>
    </tr>
</table>

As you can see all formatting, and even the functions, would be interpreted correctly.

Obviously Word is great with HTML as well, so most formatting issues can quite easily be resolved using this approach.

So how do I do this?

The solution is quite easy. In order to generate an Excel document all you need to do is to create a table with the data you want, and add the following two lines to the Page_Load event of your page:

Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader("Content-Disposition", "attachment; filename=Data.xls;");

The first line, sets the returned ContentType to Excel, and the second changes the filename of the returned file. This means that the client will be prompted to Open/Save the file, and the filename will be set to "Data.xls" rather than "Default.aspx" (or whatever your original document may be called.)

If you wanted to return a word document you'd set the ContentType to "application/msword" instead.

One little thing to consider is the fact that by default you're probably adding a lot of redundant information to your webpage. You might want to remove all excessive HTML from the page so that you remove all unnecessary headers, stylesheets, viewstate, etc. For an example, please consider the code below:

Response.Clear();
Response.Buffer = true;
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader("Content-Disposition", "attachment; filename=Data.xls;");
this.EnableViewState = false;
System.IO.StringWriter oSw = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter oHtml = new System.Web.UI.HtmlTextWriter(oSw);
Table oTable = new Table();
for (int ctr = 1; ctr <= 10; ctr++)
{
    TableRow oRow = new TableRow();
    TableCell oCell1 = new TableCell();
    oCell1.Text = ctr.ToString();
    TableCell oCell2 = new TableCell();
    oCell2.Text = (ctr*10).ToString();
    oRow.Cells.Add(oCell1);
    oRow.Cells.Add(oCell2);
    oTable.Rows.Add(oRow);
}
oTable.RenderControl(oHtml);
Response.Write(oSw.ToString());
Response.End();

 

Security issues in Office 2007

If you're running Office 2007 you might come across the following error message:

"The file you are trying to open, 'Data.xls', is in a different format than specified by the file extension. Verify that the file is not corrupted and is from a trusted source before opening the file. Do you want to open the file now?

<Yes> <No> <Help>

This is completely by design. In this example Excel has noticed that though the file is named .xls it does in fact contain html, so it's warning us about this inconsistency. We might bypass this by actually changing the file extension to htm or, perhaps saving the data as comma separated values (csv) which Excel also supports, but both options would be opened directly in the browser rather than passed on to Excel, so this is a design consideration we have to take into account.

There is really no way of bypassing this security feature other than manually disabling it, client-side, using a registry hack. (The key is HKCU\Software\Microsoft\Office\12.0\Excel\Security\ Add a DWORD named xtensionHardening and set it to 0.) For obvious reasons this is not something that is generally recommended.

Other options

There are also third-party products available for generating MS Office-compatible documents, but there are also some articles in the knowledge base on the subject. For example this little gem which can easily be adopted to ASP.NET as well:

How to use ASP to generate a Rich Text Format (RTF) document to stream to Microsoft Word

/ Johan

I've written a lot of PowerShell posts lately and here's another one. :-)

I got a question from one of the account managers if it was possible to alter the FTP Authorization Rules for a specific folder on his IIS.

The appcmd for the operation was

appcmd.exe set config "FTPFolder" -section:system.ftpServer/security/authorization /+"[accessType='Allow',users='*',roles='*',permissions='Read, Write']" /commit:apphost

He wanted to know if there was a PowerShell equivalent. Sure, you could use the appcmd directly from powershell, but if you're going to use the command line for everything, then what's the use of PowerShell?

The section in applicationhost.config that we want to edit is the following:

<location path="FTPFolder">
    <system.ftpServer>
        <security>
            <authorization>
                <add accessType="Allow" users="?" permissions="Read, Write" />
            </authorization>
        </security>
    </system.ftpServer>
</location>

 

System Specs

As you might have noticed from the screenshots already he was Running Windows 2008 with FTP 7 installed. He also had the IIS 7.0 PowerShell Provider installed.

Troubleshooting

There are a lot of cool things you can do with the WebSites using the IIS7 PowerShell provider. Below is a sample copied from iis.net. To use it properly, go to an IIS directory such as IIS:\DemoSite\DemoApp:

$winAuth = Get-WebConfiguration -filter /system.webServer/security/authentication/windowsAuthentication
$winAuth.enabled = $false
$winAuth | set-Webconfiguration -filter /system.webServer/security/authentication/windowsAuthentication -PSPath IIS:\ -location "DemoSite/DemoApp"

So judging from the sample above we should simply have to set anonymousAuthentication.enabled = $true or something like that. Unfortunately this isn't the case. If you try to access the section of applicationhost.config that we're attempting to edit (see above) you will see that we don't have any applicable properties for the object. $ftpAuth = Get-WebConfiguration -filter /system.ftpServer/security/authorization will not throw any exceptions, but using TAB to cycle through the properties of $ftpAuth will show us no immediate properties or methods of use.

Solution

The solution in this scenario is to use the Add-WebConfiguration cmdlet. The applicationhost.config can easily be translated to a valid Add-WebConfiguration call:

Add-WebConfiguration "/system.ftpServer/security/authorization"  -value @{accessType="Allow";users="?";permissions=3} -PSPath IIS:\ -location FTPFolder

The only thing that sticks out as being out of the ordinary is the permissions=3 setting. Why isn't it "Read, Write"? Actually, passing "Read, Write" as a parameter will not work. It will leave the permissions setting blank. "Read" or "Write" only will work, but not both together. I've tried figuring out if there's a valid way of passing both arguments, but so far I've drawn blanks. I've tried putting them in an array {"Read";"Write"}, passing them as "ReadWrite", "Read;Write", "Read+Write" etc. but the only way I've found so far is to pass the value 3 which is the obvious sum of the two enum values for Read and Write.

Later! / Johan

I got the following question from a reader the other day:

I've been trying to figure out how to change permissions on a folder in PowerShell. I've looked at the Get-Acl and Set-Acl, but I can only use them to copy the settings from a pre-existing object. How do I manually configure permissions?

This is actually a quite common question, so I thought I'd write a quick post on the subject.

Get-Acl and Set-Acl

To quote the PowerShell documentation "Get-Acl gets the security descriptor for a resource, such as a file or registry key." while "Set-Acl changes the security descriptor of a specified resource, such as a file or a registry key." In other words; if you want Folder_A to have the exact same permissions as Folder_B, then you simply copy the Access Control List (ACL) of Folder_B and "paste" it onto Folder_A.

$Acl = Get-Acl "C:\Folder_B"
Set-Acl "C:\Folder_A" $Acl

So far, so good.

Changing the ACL

Okay, so you want to change the ACL. Here's some sample code for how to do that:

New-Item -type directory -path C:\MyFolder
$Acl = Get-Acl "C:\MyFolder"
$Ar = New-Object  system.security.accesscontrol.filesystemaccessrule("username","FullControl","Allow")
$Acl.SetAccessRule($Ar)
Set-Acl "C:\MyFolder" $Acl

So, first we create a new folder. We then copy the ACL of that folder. We then create a new AccessRule that gives "username" full control. We then add this AccessRule to the ACL, and finally we reapply the new, altered ACL to the folder.

If we wanted to we could also have used $Acl.RemoveAccessRule($Ar) or possibly $Acl.RemoveAccessRuleAll() as well.

/ Johan

Is your mp3-collection perfectly sorted? I know mine wasn't. I thought I'd address that while creating a pretty good demo-script to show some basic filtering, script structures, etc. Since I also wanted the script to extract the metadata from every file in my collection I had to use the Shell.Application Com-object as well. All in all I think it turned out pretty well.

NOTE:

This script is provided completely as is. It is not an official product in any way and I take no responsibility for any harm it may cause.

Please note that certain metadata editors that integrate directly into the Shell may cause some severe confusion in the metadata, so I'd recommend disabling them before trying. Off course I would also recommend making a backup copy of the music library before surrendering it to the mercy of PowerShell.

If you're new to PowerShell and haven't done so already I'd recommend looking at my earlier posts on the subject:
PowerShell - An introduction, Part I
PowerShell - An introduction, Part II

The script

Param([String] $Folder)
$INVALIDCHARS = [System.IO.Path]::GetInvalidPathChars() + "/", "\", "*", "?", ":"
$MUSICATTRIBS = "*.m4a", "*.m4b", "*.mp3", "*.mp4", "*.wma", "*.flc"
$PLAYLISTATTRIBS = "*.m3u", "*.zpl"
$ALLATTRIBS = $MUSICATTRIBS + $PLAYLISTATTRIBS
$PLAYLISTSFOLDER = "Playlists" $objShell = New-Object -ComObject Shell.Application $iTotalFiles = 0 $iCurrentFile = 0 function checkFolderName($FolderName) { # Make sure the folder doesn't contain any invalid characters if(!$FolderName) { $FolderName = "Unknown" } $INVALIDCHARS | % {$FolderName = $FolderName.replace($_, "")} return $FolderName } function MoveFiles($startDir) { Write-Host "Indexing playlists..." # Do a recursive DIR in the starting directory, include everything with the attributes of a playlist. # Filter the result by excluding everything that is a Container (folder) $dirResult = get-childitem -force -path $startDir -recurse -include $PLAYLISTATTRIBS | where{! $_.PSIsContainer} if($dirResult) { Write-Host "Moving playlists..." foreach($dirItem in $dirResult) { move-item $dirItem.FullName ($PLAYLISTSFOLDER + "\" + $dirItem.Name) } } Write-Host "Indexing music..." # Do a recursive DIR in the starting directory, include everything with the attributes of a music file. # Again we filter the result by excluding everything that is a Container. $dirResult = get-childitem -force -path $startDir -recurse -include $MUSICATTRIBS | where{! $_.PSIsContainer} # Make a note of how many hits we got, so we can show the progress to the client. $iTotalFiles = $dirResult.Count if($dirResult) { foreach($dirItem in $dirResult) { # Up the counter for how many files we've processed so that we can show progress $iCurrentFile +=1 # Get the metadata for the file $fileData = getMP3MetaData($dirItem.FullName) # Find the path where we the song should be stored $ArtistPath = $fileData["Album Artist"] if (!$ArtistPath) { $ArtistPath = $fileData["Artists"] } if (!$ArtistPath) { $ArtistName = "Unknown" } # Make shure it's a valid path $ArtistPath = checkFolderName($ArtistPath) $AlbumPath = checkFolderName($fileData.Album) $ArtistPath = join-path $startDir $ArtistPath $AlbumPath = join-path $ArtistPath $AlbumPath # Check if the file should be moved if($dirItem.DirectoryName -ne $AlbumPath) { if(!(test-path $ArtistPath)) # If the Artist folder doesn't exist { MKDIR $ArtistPath | out-null } if(!(test-path $AlbumPath)) # If the Album folder doesn't exist { MKDIR $AlbumPath | out-null } move-item $dirItem.FullName ($AlbumPath + "\" + $dirItem.Name) } # Show progress $percentage = ([int](($iCurrentFile / $iTotalFiles)*100)) cls Write-Host "$percentage% ($iCurrentFile files of $iTotalFiles) complete" } } } function removeEmptyFolders($startDir) { # Get all folders and subfolders $dirResult = Get-childitem $startDir -recurse | where{$_.PSIsContainer} if($dirResult) { foreach($dirItem in $dirResult) { # If the folder is empty (Doesn't contain any music or playlists) it should be deleted if(isfolderempty($dirItem.FullName)) { # Check if the path is still valid, we may have deleted the folder already recursively. if(Test-Path $dirItem.FullName) { remove-item -path $dirItem.FullName -force -recurse } } } } } function removeOtherFiles($startDir) { # Get all non-music files (except *.jpg-files) and remove them $dirResult = Get-childitem $startDir -recurse -exclude ($ALLATTRIBS + "*.jpg") | where{! $_.PSIsContainer} if($dirResult) { foreach($dirItem in $dirResult) { if(Test-Path $dirItem.FullName) { # Make sure the file still exists, and hasn't been deleted already remove-item -path $dirItem.FullName -force } } } } function isFolderEmpty($folderPath) { # Search for any remaining music items in the folder. if(Test-Path $folderPath) { $dirChildren = get-childitem -force -path $folderPath -recurse -include $ALLATTRIBS | where{! $_.PSIsContainer} if($dirChildren -ne $null) { return $false } else { return $true } } else { # No use trying to delete the folder if it doesn't exist return $false } } function getMP3MetaData($path) { # Get the file name, and the folder it exists in $file = split-path $path -leaf $path = split-path $path $objFolder = $objShell.namespace($path) $objFile = $objFolder.parsename($file) $result = @{} 0..266 | % { if ($objFolder.getDetailsOf($objFile, $_)) { $result[$($objFolder.getDetailsOf($objFolder.items, $_))] = $objFolder.getDetailsOf($objFile, $_) } } return $result } # MAIN FUNCTION # If no argument was passed, see if anything was piped to the function instead. if (!$Folder) { $input | % { $Folder = $_ } } # Check if the path is valid, if not reset it to "" if ($Folder) { if (!(Test-Path -path $Folder)) { $Folder = "" }} # Since no valid path was given, prompt the user for a proper path. while (!$Folder) { "Please enter the full path to the folder where you wish to rearrange your MP3's" $Folder = Read-Host if ($Folder) { if (!(Test-Path -path $Folder)) { $Folder = "" }} } # Make sure we have a folder to store the playlists in. $PLAYLISTSFOLDER = join-path $Folder $PLAYLISTSFOLDER if(!(test-path $PLAYLISTSFOLDER)) # If the Playlists folder doesn't exist { MKDIR $PLAYLISTSFOLDER | out-null } # Now that everything is ready, begin rearranging the files. MoveFiles($Folder) removeEmptyFolders($Folder) removeOtherFiles($Folder)

 

Running the script

So, how do you run this script?

It's quite easy. The first step would be to copy it into notepad save it with a .ps1 extension and open up PowerShell

Execution policy

If this is the first time you try to execute a script from PowerShell you will most likely have to change the execution policy for PowerShell. I'd recommend setting it to RemoteSigned. This (obviously) means that all remote scripts must be signed or PowerShell will refuse to execute them. To change the execution policy simply type:

Set-ExecutionPolicy RemoteSigned

That's it.

If you're uncertain what your execution policy setting is you simply type:

Get-ExecutionPolicy

Okay, having changed that we can now execute the script. If you saved the script in a folder that is in your system path you only need to type the filename of the .ps1 file. (The .ps1 is optional) So, if you saved it as RearrangeMP3s.ps1 you'd write RearrangeMP3s and press enter

Breakdown

Okay, so let's take a look at the separate parts of the script.

Initial variables and constants

First we declare the parameters we're going to use. In this case it's just one: $Folder.

After that we have some constants. You may want to change $MUSICATTRIBS or $PLAYLISTATTRIBS to include, or remove, extensions based on your needs. $PLAYLISTSFOLDER is a constant which indicates in which subfolder you wish to store your playlists.

Finally we have some variables. $objShell is used for file system operations, and rather than creating a new object all the time I'd rather have a global one. $iTotalFiles and $iCurrentFile are two integers used to create a progress indicator.

Functions

After the initial variables come the sub functions called by the main function. To see what each individual function does I'd recommend looking at the comments. I've prioritized readability over minimum number of keystrokes, so yes, instead of writing:

foreach($dirItem in $dirResult)
{
	move-item $dirItem.FullName ($PLAYLISTSFOLDER + "\" + $dirItem.Name)
}

I could have written

$dirResult | % { mv $_.FullName ($PLAYLISTSFOLDER + "\" + $_.Name) }

But in this case I think that would have been counterproductive.

The main function

Finally we have the main function. It is written last in the script. Why is that? Well, actually PowerShell is the most sequential language you'll ever come across. It will not bother to check the entire script for function declarations. Instead it will go through the script one line at a time. This means that the following script will not work:

SayHello

function SayHello
{
	Write-Host "Hello World"
}

When you call SayHello in the sample above the function has not yet been declared, so PowerShell will throw an exception in your face saying that SayHello is unrecognized.

Footnote

There is actually a supported way of achieving the very same thing, but where's the fun in that?

  • Open up Windows Media Player
  • Go to Options -> Library and tick the checkbox for "Rearrange music in rip folder"
  • Go to the "Rip Music"-tab
  • Make sure the path is correct in the "Rip music to this location"-section
  • Click OK
  • Select all files in the library
  • Right-click and choose the "Advanced Tag Editor"-option
  • Do not change anything. Simply click Apply
  • Windows Media Player will now rearrange all the files using your Rip settings.

/ Johan

This is a continuation of part I. If you haven't read it I suggest at least going through the summary at the end of the post.

Working with Drives

In your basic command prompt you have the normal file system drives that you use to access your hard-drive, floppy, DVD, USB-stick, network shares etc. PowerShell uses this established interface to access other data sources as well. The following providers are accessible by default through drives in PowerShell 1.0:

  • Registry (HKLM: or HKCU:)
  • Certificate store (Cert:)
  • Environment variables (Env:)
  • Aliases (Alias:)
  • Functions (Function:)
  • Variables (Variable:)

When you install extensions for like the one fore IIS, etc. you usually get another drive accessible where you can look at the current webapplications and check their properties.

Navigating

Selecting one of the PowerShell drives is easy. It is almost like in MS-DOS.

PS C:\>CD HKLM:
PS HKLM:\> DIR

You can use the same commands as you'd do in MS-DOS, such as CD, DIR, MKDIR, etc.

To get a certain property for a registry key, (or a regular folder for that matter) you use the Get-ItemProperty cmdlet.

PS HKLM:\> CD system
PS HKLM:\system> CD CurrentControlSet\Control
PS HKLM:\system\CurrentControlSet\Control> Get-ItemProperty CrashControl
 
PSPath           : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\system\CurrentControlSet\Control\CrashControl
PSParentPath     : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\system\CurrentControlSet\Control
PSChildName      : CrashControl
PSDrive          : HKLM
PSProvider       : Microsoft.PowerShell.Core\Registry
AutoReboot       : 1
CrashDumpEnabled : 2
Overwrite        : 1
LogEvent         : 1
DumpFile         : C:\Windows\MEMORY.DMP
MinidumpDir      : C:\Windows\Minidump

All in all it's more or less the same working with Aliases or Environment variables. Note the following difference in output, though:

PS Env:\> Dir Env:PROCESSOR_ARCHITECTURE
 
Name                           Value
----                           -----
PROCESSOR_ARCHITECTURE         AMD64
 
PS Env:\> $Env:PROCESSOR_ARCHITECTURE
AMD64
PS Env:\>

Did you note how putting a $-sign before the variable name gave us the value of the variable instead of a complete object? This brings us quite nicely to the subject of variables.

Variables

Variables aren't that tricky to work with. They do not need to be pre-initiated. They do, however, require a $ to be identified as variables. Otherwise you get the following error message:

PS C:\> $Name = "Johan"
PS C:\> Name = "Johan"
The term 'Name' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:5
+ Name  <<<< = "Johan"

PS C:\>

To display the value of a variable you simply type the name of the variable and PowerShell kindly returns the contents

PS C:\> $Name
Johan

When it comes to strings, you can choose for yourself if you want to use double quotes or single quotes, but there is one minor difference:

PS C:\> $str1 = "Hello"
PS C:\> $str2 = "World!"
PS C:\> $strCombo1 = "$str1 $str2"
PS C:\> $strCombo2 = '$str1 $str2'
PS C:\> $strCombo1
Hello World!
PS C:\> $strCombo2
$str1 $str2
PS C:\>

As you can see, a variable within double quotes will be evaluated, while within single quotes it will be interpreted literally.

Apart from strings you can also store integers and dates. Integers do not need delimiters and dates are most easily created using the Get-Date function

PS C:\> $birthday = 11/15/1973

 

Arrays

Declaring Arrays is a breeze. PowerShell will automatically Split any comma-separated sequence of values so creating a arrays is as easy as this:

PS C:\> $Colors = "Red", "Green", "Blue"

The arrays are also dynamic by default, so adding values to them is a simple matter as well.

PS C:\> $Colors += "Cyan", "Magenta", "Yellow"

Creating two-dimensional arrays are done by declaring each row of the array as a one-dimensional array and then adding them to another array:

PS C:\> $arr1 = 1, 2, 3
PS C:\> $arr2 = "A", "B", "C"
PS C:\> $arr3 = $arr1, $arr2
PS C:\> $arr3[0][0]
1
PS C:\> $arr3[1][1]
B
PS C:\>

 

User input

Finally, to get dynamic user input you can use the Read-Host cmdlet.

$Name = Read-Host "What is your name?"

 

Summary

Okay, so to summarize we have now covered the following:

  • PowerShell uses system drives to access the registry, environment variables, etc.
  • Variables are preceded with a $
  • To access the value of a variable, simply type it's name.
  • Arrays are comma-separated lists of variables
  • User input can be handled by the Read-Host cmdlet

Next post will probably cover some script logic such as if-clauses, looping, etc. After that I think it will be time to post some sample scripts.

/ Johan

Okay, after a summer break that no doubt got a little longer than expected I am now I'm now back in the saddle again.

Next post will most likely be a follow-up on the PowerShell post I wrote a while back. Stay tuned.

/ Johan

As you're probably well aware IIS7 relies heavily on a master configuration file named ApplicationHost.config. Now, instead of using a history file like IIS6 did, IIS7 will check this file every 2 minutes to see if it's changed. If the file has been updated, then a backup copy of the old .config will be stored in the inetpub\history-directory.

Configuration history

This is really cool and a very useful feature. There's only one little caveat in my opinion and that's the fact that by default IIS7 will only save up to 10 backups. All the other backups will be discarded. This means that if you're spending 20 minutes fiddling with the settings in ApplicationHost.config you will quite likely have overwritten all your old backups. Now, if something goes wrong at that point and you decide to spend another 20 minutes trying to fix this problem by further editing the ApplicationHost.config you will surely have overwritten the backups.

In a worst case scenario you might even have gone from Healthy Server to Unstable Server to Server Down and you now find that you aren't even able to restore it to the Unstable state.

My suggestion is to increase the default number of backups to 100, or why not 1000? You can then feel certain that any backups taken aren't overwritten when you try to undo the very same changes. Here's how you do it:

  1. Go to %windir%\system32\inetsrv\config\
  2. Open applicationHost.config in notepad
  3. Locate the system.applicationHost section group
  4. Add the following item to the section group:
    <configHistory maxHistories="100" period="00:02:00" />
    (Naturally the maxHistories value can be whichever value you prefer)
  5. Save

To restore one of the previous configurations, simply go to inetpub\history\cfgHistory_[nnnnnnnnnn] and copy the config from there, or use appcmd. (See below)

Backup using appcmd

Now that we've done this there is one more thing I'd strongly recommend doing:

  1. Open up a command-line with administrative priviliges
  2. Go to %windir%\system32\inetsrv
  3. Run "appcmd add backup "[Name of your backup]""

What you've done now is to create a manual backup of your current config. It's always good to have a decent (and persistent) restore point to return to.

If you wish to restore this backup you simply use the following syntax: "appcmd restore backup "[Name of your backup]"". You can also delete a backup using the "delete" keyword, or you can list all backups, including the ones generated by the Configuration history, like this: "appcmd list backup"

Okay, so now we have made sure we have some extensive automatic backups in place which might save us a lot of headaches later on.

 

Later! / Johan

Over the past months I’ve been holding a couple of workshops on PowerShell. It’s quite an interesting topic, but not something I originally thought would be suitable for this blog and its web-development theme. Still, having given this a bit more thought I decided that there are so many useful features and cmdlets that could be of great value when hosting a web site or two that I should reconsider.

This first post will be a brief introduction for those who’ve never touched PowerShell before. Later posts will probably be more hands-on and less theoretical. I’ll gladly receive any thoughts or comments you may have.

Installing

PowerShell can be downloaded from the Microsoft web site. Simply point your browser to http://www.microsoft.com/powershell and you’ll be set in no time. Apart from the executables you’ll find a lot of useful links, like the one to the PowerShell team blog and the IIS 7.0 PowerShell Provider.

Basic syntax

Okay, having installed PowerShell we’re now ready to try some basic commands.

Echoing text

Any object not used in an assignment or operation will be echoed to the screen. This means that if we simply type a string like this:

PS C:\>“Hello World”
Hello World

It will be displayed on the screen. (Without the quotes.) The same goes for numbers, so by typing an equation you’ll be given the result:

PS C:\>1 + 1
2

If you type in an array PowerShell will echo it one line at a time:

PS C:\>“foo”, “bar”, “bletch”, “fum”
foo
bar
bletch
fum

Basically you could say that any object will, unless told otherwise, be displayed on screen using its default formatting. The default formatting is defined by the object itself.

Calling executables

Running an executable is more or less as easy as in the regular command shell. At least if the .exe is in the PATH. For any executable not in the PATH you'll need to write the full path of the .exe for it to successfully execute. This may seem like a hassle, but this behavior is by design and was implemented in order to prevent you from accidentally executing an .exe.

Cmdlets

Cmdlets (pronounced "command-lets") are PowerShell-specific commands. They follow a strict naming standard of verb-noun, (for example "get-date" or "set-location").

Get-Help

This a command that you'll use quite a lot. Get-Help <cmdlet> will display some generic information about the cmdlet in question. Using various switches such as "-detailed", "-examples" or "-full" you can then get more detailed information on the cmdlet in question. For example:

Get-Help Get-Date -examples

This will show you the generic information on get-date as well as some sample code.

Set-Location

Set-Location is the PowerShell equivalent to the old MS-DOS command "CD". Some examples are:

Set-Location ..
Set-Location C:\Windows\
Set-Location D:

 

Aliases

Actually you can write CD in PowerShell too. By default PowerShell will set up an alias for you that points to the Set-Location cmdlet. This means that CD is just another name for Set-Location. If you want you can create your own aliases using the New-Alias cmdlet:

New-Alias gh Get-Help

This creates an alias named gh that points at Get-Help. If you type Get-Help gh PowerShell will be smart enough to show you the documentation for the Get-Help cmdlet.

Object orientation

One of the cool things about PowerShell is that it is object oriented. This means that all the cmdlets return objects, which can, in turn, be passed on to another cmdlet. To illustrate this, let’s look at the DIR-command.

Using DIR

You can use DIR in PowerShell, but it is actually an alias for the get-childitem cmdlet. How can you tell? Use the get-command cmdlet:

PS C:\> get-command dir
 
CommandType     Name                                                Definition
-----------     ----                                                ----------
Alias           dir                                                 Get-ChildItem

Since DIR in PowerShell is not the same thing as DIR in MS-DOS that means you can’t use all the switches you might be used to, for example DIR /w will not work.

So, what happens when you type DIR and press enter?

Well, first of all the get-childitem cmdlet is run. It looks in your present working directory, creates a collection of all the items there, and returns it. You can think of this collection as a big Excel Spreadsheet if you like.

Once the collection is returned to PowerShell it will be displayed on screen. This is because the default behavior for PowerShell when it comes to any object is to display it on screen. We saw this earlier with strings, integers and arrays.

The collection will be displayed using a default presentation. This does not mean you will see all the information the object contains. If I run Get-ChildItem (DIR) on a folder I’ll get the standard information. Like so:

PS C:\Test> dir
 
    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Test
 
Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2008-05-28     11:25      45568 Document.doc
-a---        2008-05-26     10:00    8993629 Satisfaction.mp3
-a---        2008-05-28     11:25       1599 Text.txt

Even though I get only text on screen the actual object returned is much more complex. I can demonstrate this by piping the object into another cmdlet.

Piping

A simple MS-DOS piping example would be type file.txt | more. Actually you can use more in PowerShell as well, but let’s look at another, more interesting cmdlet that we could pipe the object into; Format-List.

Format-List

By default the output of Get-ChildItem is formatted in a table, but let’s say we want it as a list instead? No problem. Simply use the following syntax:

PS C:\Test> dir | format-list
 
    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Test
 
Name           : Document.doc
Length         : 45568
CreationTime   : 2008-05-28 11:22:53
LastWriteTime  : 2008-05-28 11:25:26
LastAccessTime : 2008-05-28 11:25:26
VersionInfo    :
 
Name           : Satisfaction.mp3
Length         : 8993629
CreationTime   : 2008-05-28 11:24:33
LastWriteTime  : 2008-05-26 10:00:46
LastAccessTime : 2008-05-28 11:24:33
VersionInfo    :
 
Name           : Text.txt
Length         : 1599
CreationTime   : 2008-05-28 11:22:53
LastWriteTime  : 2008-05-28 11:25:35
LastAccessTime : 2008-05-28 11:22:53
VersionInfo    :

We still have the default information, only presented in a different manner. Still, there is a lot more information we could get, by using the –property switch. By simply using the following syntax: dir | format-list –property * we get all the properties of each and every item in the directory. The output is quite extensive:

PS C:\Test> dir | format-list -property *
 
PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Test\Document.doc
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Test
PSChildName       : Document.doc
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
PSIsContainer     : False
Mode              : -a---
Name              : Document.doc
Length            : 45568
DirectoryName     : C:\Test
Directory         : C:\Test
IsReadOnly        : False
Exists            : True
FullName          : C:\Test\Document.doc
Extension         : .doc
CreationTime      : 2008-05-28 11:22:53
CreationTimeUtc   : 2008-05-28 09:22:53
LastAccessTime    : 2008-05-28 11:25:26
LastAccessTimeUtc : 2008-05-28 09:25:26
LastWriteTime     : 2008-05-28 11:25:26
LastWriteTimeUtc  : 2008-05-28 09:25:26
Attributes        : Archive
 
PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Test\Satisfaction.mp3
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Test
PSChildName       : Satisfaction.mp3
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
PSIsContainer     : False
Mode              : -a---
Name              : Satisfaction.mp3
Length            : 8993629
DirectoryName     : C:\Test
Directory         : C:\Test
IsReadOnly        : False
Exists            : True
FullName          : C:\Test\Satisfaction.mp3
Extension         : .mp3
CreationTime      : 2008-05-28 11:24:33
CreationTimeUtc   : 2008-05-28 09:24:33
LastAccessTime    : 2008-05-28 11:24:33
LastAccessTimeUtc : 2008-05-28 09:24:33
LastWriteTime     : 2008-05-26 10:00:46
LastWriteTimeUtc  : 2008-05-26 08:00:46
Attributes        : Archive
 
PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Test\Text.txt
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Test
PSChildName       : Text.txt
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
PSIsContainer     : False
Mode              : -a---
Name              : Text.txt
Length            : 1599
DirectoryName     : C:\Test
Directory         : C:\Test
IsReadOnly        : False
Exists            : True
FullName          : C:\Test\Text.txt
Extension         : .txt
CreationTime      : 2008-05-28 11:22:53
CreationTimeUtc   : 2008-05-28 09:22:53
LastAccessTime    : 2008-05-28 11:22:53
LastAccessTimeUtc : 2008-05-28 09:22:53
LastWriteTime     : 2008-05-28 11:25:35
LastWriteTimeUtc  : 2008-05-28 09:25:35
Attributes        : Archive

As long as something is returned it is possible to daisy-chain additional cmdlets onto the previous one. We could, for example, add | more to the command above to divide it into smaller chunks. We could use the Out-File cmdlet to save the result to a textfile, etc.

Summary

What have we covered so far?

  • PowerShell is object-oriented
  • By default any object will be displayed on screen, so if you want to know what 2+2 equals, simply type 2+2.
  • You can call standard .exe applications, etc from PowerShell, but they either need to be in the PATH, or you need to write the full path to them.
  • PowerShell-specific applications are called cmdlets (“Command-lets”)
  • Most MS-DOS commands work, but they’re usually aliases to their PowerShell equivalent, so you might not be able to use all the switches you’re used to.
  • You can use the | to daisy-chain cmdlets together, such as dir | format-list | more

We still haven’t covered things like functions, variables, creating .NET objects, etc. I also plan to write a few samples showing how to use the cool cmdlets to get information from computers on the network, looking for specific files, exporting to Excel, printing, etc. As always I’d be happy to receive feedback and suggestions on things to cover.

/ Johan

I got the following question in my Getting started with windbg – post and I thought it might be worth posting the replies in a separate article:

Hi Johan,

about those threads with an ID of XXXX, should they go away after certain amount of idle time like 2 minutes? I am trouble shooting an application while the application should sit idle (because of no stimulation), however it is still using quite some CPU time and and threadpool threads, where it should not. So I created an adplus dump when it is sitting idle, and I found that there are many threads with ID XXXX, and I created a dump file again after 15 minutes, again, it still have the exact same XXXX threads. All those threads are completion port threads, and I wonder why they did not get recycled?

Also, when doing a '~*e !clrstack', most of the worker threads and completion port threads are showing "Failed to start stack walk: 80004005". Is there a way to show the stack for those threads, because those are the threads I am interested in.

Thank you!


The GC will be triggered when you’re allocating memory. If your application is inactive, then so is the Garbage Collector. Even if you’re making a few, random allocations they still may not be enough to trigger a GC, so this is not at all unexpected.

The “Failed to start stack walk: 80004005”-error is displayed when the thread did contain a managed stack, but no longer does.

/ Johan

More Posts Next page »
 
Page view tracker