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

Tuesday, 31 March 2015

How to recreate a windows folder and file structure with empty (zero byte) files

I have an external drive full content that I reference only periodically. I don’t connect the drive regularly to my laptop but I often want to know if a file is on that drive. I recently thought it would be helpful to have a zero-byte copy of the drive contents on my local machine.

Although I thought about writing a PowerShell script to accomplish this, robocopy includes a feature to do exactly what I want through the /Create command:

robocopy source dest /E /Create

Robocopy has been bundled with Windows since Vista.

Wednesday, 19 January 2011

Centralising global TFS alerts/notifications

TFS email alerts are critical to keeping your team informed about changes in the TFS projects they care about. At the very least, most users probably want to know when they've been assigned a work item.

Although Visual Studio 2010 includes the Alerts Explorer (Team –> Alerts Explorer) for signing yourself up for alerts, there's no obvious way to do the same thing for a group of users. Adding custom business logic into the mix really means you'll need to find a better alternative.

Behold! My latest Codeplex project is live, I call it TFS Global Alerts:

http://tfsalerts.codeplex.com/

When we last faced this problem with Visual Studio Team System 2008, we landed on a solution from vertigo.com--I'd link to the original blog post but it's been removed from the site. Moving forward a few years, it was recently time to upgrade our TFS 2008 install to TFS 2010 and the question naturally arose about what we do with alerts: continue using the Vertigo web service or not?

The custom logic originally added to the Vertigo solution was flailing a bit due to the kludge of time so we decided to refactor the current solution to better meet our needs and keep everything working with TFS 2010. I've uploaded a generalised version of the end result for your alerting enjoyment as an example of handling TFS events in a web service (you may want to follow the links below for alternatives based on Windows services and event listeners).

The Solution

TFS Global Alerts is, at its core, a web service containing a single public method:

[WebMethod]
public void Notify (string eventXml)

Notify() accepts a single parameter, a string containing XML detailing the change that occurred and some information about the work item itself. The Notify() method signature changed slightly from TFS 2008: it previously included a second string parameter, tfsIdentityXml. I removed this parameter to get things wired up successfully with TFS 2010.

Most elements in the eventXml look a bit like this—note the OldValue and NewValue child elements:

<Field>
<Name>State</Name>
<ReferenceName>System.State</ReferenceName>
<OldValue>Active</OldValue>
<NewValue>Resolved</NewValue>
</Field>

With the Notify() method called and most of the data you need at hand, sending an email to the relevant parties is then quite simple. TFS Global Alerts does some additional work to exclude the person who made the change from receiving a notification about that change (presumably they know what they've just done!) and avoid sending notifications about code check-ins linked to a work item.

The only real complexity in this code is retrieving an email address for each user from Active Directory. In our production environment I found TFS listed users by display name however in development, with TFS running as a service account in the dev domain, users were returned as domain\username. In production, if a user had multiple accounts (some of our admins), their name was returned as "First Last (domain\username)".

In terms of deployment, I simply deploy the web service to the web server hosting TFS itself and TFS web access.

The notification system hangs off TFS' own event subscription tool, bissubscribe.exe (you'll find it at C:\Program Files\Microsoft Team Foundation Server 2010\Tools\bissubscribe.exe). TFS raises events for all sorts of different happenings and bissubscribe.exe allows you to subscribe an email address or SOAP web service to handle those events.

Since I had some problems creating a server-level subscription, here's the command I use to create a project collection subscription (which captures all events in our case):

bissubscribe /eventType WorkItemChangedEvent /address http://localhost:8080/{vdir_path}/Alerts/WorkItemChanged.asmx /collection http://localhost:8080/tfs/{project_collection_name}

Result:

TF50001:  Created or found an existing subscription. The subscription ID is 36

Debugging

TFS 2010 only sends alerts every two minutes by default and events subscribed to via bissubscribe.exe adhere to the same policy. This can be a bit annoying when you're developing so you may want to dial down the batch wait time. I've included a Powershell script in the solution to configure this but otherwise, check out chrisid's post on the subject.

For additional help debugging bissubscribe, Grant Holliday has some useful tips.

Finally, don't forget to actually configure your TFS server to send email!

Resources

If you found this post helpful, please support my advertisers.

Thursday, 20 May 2010

Disable the Output Cache Programmatically with PowerShell

Restoring a content database from a production environment to a development machine for dev purposes can sometimes get a little bit interesting.

Our production environments generally have output caching enabled for performance reasons(see /_Layouts/sitecachesettings.aspx in your own environment) but developing or troubleshooting an issue in dev normally requires output caching be disabled to you can view the results of your work. Of course the output cache settings are stored within the site collection so using a production backup will normally bring the output cache setting along with everything else.

Of course you can manually disable output caching but that means remembering to do so. As our attach process is scripted in a batch file for repeatability and automation purposes, I recently added a step to disable the output cache programmatically. To do this, I wrote a little PowerShell script and call it from the batch file:

Param($Url)

write-host "User must be a site collection administrator or have full control within web application policy!"

[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint”)
[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Publishing”)

$cacheSettings = new-object Microsoft.SharePoint.Publishing.SiteCacheSettingsWriter($Url);
$cacheSettings.EnableCache = $false;

$cacheSettings.Update();

The script takes a single named parameter (Url) which specifies the site collection URL. As you can see, I'm working with the SiteCacheSettingsWriter class to set the EnableCache property to false before sending through an Update() command; of course the same thing can easily be accomplished in C#.

I call the script like so from my batch script:

powershell "& ./DisableOutputCache.ps1 -Url http://dev-moss"

I've parameterised this script since I restore content databases from multiple sites and didn't want the URL hard-coded.

As per my note, the account used to execute this script must be a site collection owner or have rights configured the web application policy or you'll encounter an access denied error.

 

How to supply named arguments to a PowerShell script

Calling a parameterised PowerShell script from a batch file or command window using name parameters is pretty easy, just flag each paramater name and supply a value like so:

powershell.exe "& ./MyScript.ps1 -MyParam1 'MyValue 1' –MyParam2 MyValue2"

Note I’ve wrapped MyValue1 in single quotes because of the space; the single quotes are stipped off the argument when it’s presented to the script.

MyScript.ps1 would use the parameters like so:

Param($MyParam1, $MyParam2)
write-host $MyParam1
write-host $MyParam2

The Param statement declares the formal parameters accepted by the script and must be the first executable line of code in the script. The parameters can now be used like any other variable, as demonstrated by the two write-host calls.

Disclaimer: I’m a PowerShell rookie! 

Saturday, 16 January 2010

Manage Active Directory Users and Groups with PowerShell

With a new dawn and the SharePoint 2010 beta upon us, I figured it was finally time to a) dip my toes into the PowerShell pool and b) learn how to automate the creation of users, groups, organisational units, and other objects within Active Directory. In the PS case, it still seems like an unnecessary evil and, between all the other things on my “TO LEARN” list, it hasn’t been a high priority; that said, it seems to work and I kind of, sort of, maybe just like it. We’ll see… On the automation front, I figured I’ll probably blow away more than my fair share of development environments before my time is up so I might as well make creating the darned things a little bit easier and more repeatable.

My search for information along these lines quickly led me to Quest’s ActiveRoles Management Shell for Active Directory—a set of PowerShell commandlets. The download is free and if you grab it I’d also strongly suggest you pull down the admin guide as well—it’s comprehensive and includes examples. To install, run the .msi and finish the wizard with the defaults.

From there you’ll need to add the related snap-in in a PS window as follows:

Add-PSSnapin Quest.ActiveRoles.ADManagement

The first thing I wanted to create was a user object and this was reasonably straightforward. Be warned some of the commandlets take a lot of arguments, the names of which don’t always correspond to the UI you see in the AD Users and Computers snap-in: password is –UserPassword, for example, and Job Title is simply –Title. Here’s the command I used:

new-QADUser -name 'Bob' -ParentContainer 'OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -SamAccountName 'Bob' -UserPassword 'TH1Smis1s' -FirstName 'Bob' -DisplayName 'Bob' -UserPrincipalName 'bob@spsdev.mediawhole.com' -Title CEO

This creates the user but new-QADUser doesn’t allow you set all of the things I want to using the standard parameters. In my case, I also want to set the account to never expire. To do this, I used the set-QADUser commandlet as well, which seems to let you get to much more detail:

set-QADUser 'CN=Bob,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -PasswordNeverExpires $true

Next I created a new group using the new-QADGroup commandlet:

new-QADGroup -ParentContainer 'OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -name 'Managers' -samAccountName 'Managers' -grouptype 'Security' -groupscope 'Global'

…before adding my new user to my new group with the add-QADGroupMember commandlet:

add-QADGroupMember -identity 'CN=Managers,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -member 'CN=Bob,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com'

This last exercise proved somewhat troublesome as the examples in the admin guide and specific documentation about the –member parameter indicated I could supply ‘spsdev\Bob’ in place of the string above I ended up using successfully. The error message was fairly explicit about this:

Add-QADGroupMember : Cannot resolve directory object for the given identity: 'spsdev.mediawhole.com\bob'.

One other commandlet I haven’t yet managed to get working is the new-QADObject commandlet to create a new OU:

new-QADObject -ParentContainer 'DC=spsdev,DC=mediawhole,DC=com' -type 'organizationalUnit' -NamingProperty 'ou' -name 'Dirty Words'

This command fails on the type parameter with;

New-QADObject : A referral was returned from the server…

+ CategoryInfo          : NotSpecified: (:) [New-QADObject], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Quest.ActiveRoles.ArsPowerShellSnapIn.Powers
hell.Cmdlets.NewObjectCmdlet

I’ll probably look back at this post one day after figuring this one out and laugh at my lack of understanding but for now it’s got me stumped.