Trevor Sullivan's Tech Room

Minding the gap between administration and development

Posts Tagged ‘function’

PowerShell: Finding Friday the 13th

Posted by Trevor Sullivan on 2012/01/13


Update (2012-01-13): Justin Dearing (aka @zippy1981) informed me that it would be more efficient to look at the 13th of each month, and test if it was a Friday. In theory at least, he’s absolutely correct; I wrote the function the first way I thought of it, and I always welcome suggested improvements.

This morning I noticed that it was Friday the 13th. No, I didn’t realize it by looking at the Windows 7 system clock. I realized it because I had the worst morning waking up for the past month. I started wondering when the next Friday the 13th would be, and how often they occurred. To satisfy my curiosity, I immediately thought to write a PowerShell advanced function to find them! This was also partially inspired by Jeff Hicks’ posting 13 PowerShell scriptblocks for Friday the 13th.

There are two parameters to this function:

  • StartDate (default to "today")
  • EndDate (default to "today" +1460 days, which is roughly 4 years in the future)

You can call this function using neither parameter, one of them, or both of them. Both parameters are [System.DateTime] structs, and PowerShell will automatically try to parse any string value passed into them. Here is an example:

Find-Friday13th -StartDate 2000-01-01 -EndDate 2005-01-01

And here is the function!

<#
    .Synopsis
    This function finds Friday the 13ths.

    .Parameter StartDate
    The date you want to begin searching for Friday the 13ths.
    .Parameter EndDate
    The end date you want to search for Friday the 13ths.

    .Outputs
    [System.DateTime] objects that represent Friday the 13ths.

    .Notes
    Written by Trevor Sullivan (pcgeek86@gmail.com) on Friday, January 13, 2012.
#>
function Find-Friday13th {
    [CmdletBinding()]
    param (
        [DateTime] $EndDate = ((Get-Date) + ([TimeSpan]::FromDays(1460)))
        , [DateTime] $StartDate = (Get-Date)
    )

    # Inform user that the $EndDate parameter value must be greater than the $StartDate parameter value
    if ($EndDate -lt $StartDate) {
        Write-Error -Message 'The EndDate must be greater than the StartDate!';
        return;
    }

    # Get the next Friday after $StartDate
    while ($StartDate.DayOfWeek -ne 'Friday') {
        Write-Host "Finding next Friday";
        $StartDate = $StartDate.Add([TimeSpan]::FromDays(1));
    }

    # While $StartDate is less than $EndDate, add 7 days
    while ($StartDate -lt $EndDate) {
        # If the Day # is 13, then write the [DateTime] object to the pipeline
        if ($StartDate.Day -eq 13) {
            Write-Output -InputObject $StartDate;
        }
        # Add 7 days to $StartDate (next Friday after current)
        $StartDate = $StartDate.Add([TimeSpan]::FromDays(7));
    }
}

# Call the function
Find-Friday13th -EndDate 2017-12-31

 

Here’s what the function’s output looks like. The objects returned to the pipeline are all [System.DateTime] objects, which are automatically being ToString()’d.

image

Posted in .NET, powershell, scripting | Tagged: , , , , , , , , | 2 Comments »

PowerShell: Move ConfigMgr Collections

Posted by Trevor Sullivan on 2012/01/12


Introduction

If you work with Microsoft System Center Configuration Manager (SCCM / ConfigMgr) 2007 in any capacity, you probably are familiar with the concept of "collections" and how painful they can be to work with sometimes. The ConfigMgr console does not provide any method of moving a collection from one parent to another, and the GUI is pretty slow to work with.

image

So what’s the solution here? PowerShell, of course!

PowerShell Code

Here is a PowerShell function that will allow you to move a ConfigMgr collection either by name or by collection ID.

Note: Select all of the function text top-to-bottom, and you can retrieve the text that is cut off towards the right.

<#
    .Synopsis
    This function allows you to re-assing the parent for a ConfigMgr collection to a new collection ID

    .Author
    Trevor Sullivan (pcgeek86@gmail.com)

    .Example
    c:\PS> Move-SccmCollection -SccmServer sccm01 -SiteCode LAB -CollectionID LAB00159 -ParentCollectionID LAB000150;

    Description
    -----------

    This command moves the ConfigMgr collection with ID "LAB000159" to being a child of collection ID "LAB000150".

    .Example
    c:\PS> Move-SccmCollection -SccmServer sccm01 -SiteCode LAB -CollectionName 'Visual Studio' -ParentCollectionID Microsoft;

    Description
    -----------

    This command moves the ConfigMgr collection named "Visual Studio" to being a child of the collection named "Microsoft". Note that you do not need to specify quotes around the parameter value if it does not contain spaces.

    .Notes
    This function is untested with collection links. It is not known whether or not this will remove existing collection links.
#>
function Move-SccmCollection {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] [string] ${SccmServer}
        , [Parameter(Mandatory = $true)] [string] ${SiteCode}
        , [Parameter(ParameterSetName = "ByCollectionID", Mandatory = $true)] [string] ${CollectionID}
        , [Parameter(ParameterSetName = "ByCollectionID", Mandatory = $true)] [string] ${ParentCollectionID}
        , [Parameter(ParameterSetName = "ByCollectionName", Mandatory = $true)] [string] ${CollectionName}
        , [Parameter(ParameterSetName = "ByCollectionName", Mandatory = $true)] [string] ${ParentCollectionName}
    )

    # Set-PSDebug -Strict;

    # Ensure that ConfigMgr site server is available
    if (-not (Test-Connection -ComputerName $SccmServer -Count 1)) {
        return;
    }

    # Obtain references to collection and parent collection
    switch ($PSCmdlet.ParameterSetName) {
        # Use the "ByCollectionID" PowerShell parameter set to retrieve collection references by ID
        'ByCollectionID' {
            ${CollectionRelationship} = @(Get-WmiObject -ComputerName $SccmServer -Namespace root\sms\site_$SiteCode -Class SMS_CollectToSubCollect -Filter "subCollectionID = '$CollectionID'")[0];
            ${Collection} = @([wmi]("\\{0}\root\sms\site_{1}:SMS_Collection.CollectionID='{2}'" -f ${SccmServer}, ${SiteCode}, ${CollectionID}))[0];
            ${ParentCollection} = @([wmi]("\\{0}\root\sms\site_{1}:SMS_Collection.CollectionID='{2}'" -f ${SccmServer}, ${SiteCode}, ${ParentCollectionID}))[0];
        }
        # Use the "ByCollectionName" PowerShell parameter set to retrieve collection references by name
        'ByCollectionName' {
            ${Collection} = [wmi](@(Get-WmiObject -ComputerName $SccmServer -Namespace root\sms\site_$SiteCode -Class SMS_Collection -Filter ("Name = '{0}'" -f ${CollectionName}))[0].__PATH);
            ${ParentCollection} = [wmi](@(Get-WmiObject -ComputerName $SccmServer -Namespace root\sms\site_$SiteCode -Class SMS_Collection -Filter ("Name = '{0}'" -f ${ParentCollectionName}))[0].__PATH);
            ${CollectionRelationship} = @(Get-WmiObject -ComputerName $SccmServer -Namespace root\sms\site_$SiteCode -Class SMS_CollectToSubCollect -Filter ("subCollectionID = '{0}'" -f ${Collection}.CollectionID))[0];
        }
    } 
    
    # If references to both the child and [new] parent collection were obtained, then move on
    if (${Collection} -and ${ParentCollection}) {
        Write-Verbose -Message ('Setting parent collection for {0}:{1} to {2}:{3}' -f `
            ${Collection}.CollectionID `
            , ${Collection}.Name `
            , ${ParentCollection}.CollectionID `
            , ${ParentCollection}.Name);
        ${CollectionRelationship}.parentCollectionID = ${ParentCollection}.CollectionID;
        # Create the new collection relationship (this [oddly] spawns a NEW instance of SMS_CollectToSubCollect), so we have to clean up the original one
        ${CollectionRelationship}.Put();

        # Clean up all other collection relantionships for this collection
        ${OldCollectionRelationshipList} = @(Get-WmiObject -ComputerName $SccmServer -Namespace root\sms\site_$SiteCode -Class SMS_CollectToSubCollect -Filter ("subCollectionID = '{0}' and parentCollectionID <> '{1}'" -f ${Collection}.CollectionID, ${ParentCollection}.CollectionID));
        foreach (${OldCollectionRelationship} in ${OldCollectionRelationshipList}) {
            ${OldCollectionRelationship}.Delete();
        }
    }
    else {
        Write-Warning -Message 'Please ensure that you have entered a valid collection ID or name';
    }
}

 

Here is an example of how to use this function to move a collection based on their collection IDs:

Move-SccmCollection -SccmServer sccm01.mybiz.loc -SiteCode LAB -CollectionID LAB00011 -ParentCollectionID LAB00022;

Here is an example of how to use the function to move a collection based on the collection name:

Move-SccmCollection -SccmServer sccm01.mybiz.loc -SiteCode LAB -CollectionName ‘Visual Studio’ -ParentCollectionID Microsoft;

Posted in configmgr, powershell, scripting, tools, wmi | Tagged: , , , , , , , , , , , , , , , , | Leave a Comment »

PowerShell: Dynamic Parameters and Parameter Validation

Posted by Trevor Sullivan on 2010/11/15


Background on Parameter Validation

PowerShell advanced functions allow their creators to specify a fair amount of metadata that describes their parameters. One huge benefit of parameter declarations in PowerShell is that it’s possible to validate input right at the parameter level, before you execute any code in the body of the function. This helps make code more readable, by keeping the parameter validation code up with the parameters themselves, rather than somewhere inside the function’s body.

There are around 10 different built-in parameter validation attributes, including validation against regular expressions, a pre-defined set of values, or $null values. As a catch-all, there is also an attribute (called ValidateScript) that allows you to validate a parameter against arbitrary code. This is a truly powerful feature, which also shares a significant drawback that I will cover in more detail below. For the most part, these validation parameters work great, and are very easy to implement. While this is all nice in theory, unfortunately there are circumstances where it’s necessary to use other validation measures. Here is a mindmap that I’ve been working on, part of which contains some tidbits of information about PowerShell validation attributes:

PowerShell Mindmap - Advanced Parameters

Failing the built-in validation attributes, parameter validation can also occur within a function body, ideally in the BEGIN {…} block where it occurs before any objects are processed through the pipeline. Another powerful method of parameter validation is the use of PowerShell’s dynamic parameters.

Dynamic parameters

Benefits

Dynamic parameters are great, in theory. You can write arbitrary PowerShell code inside the code block where the parameters are defined. Perhaps the most significant benefit, among the few that there are, is the inherent ability for dynamic parameters to see the values of their siblings: static parameters. This exposes the drawback to the ValidateScript attribute that I mentioned before — although using ValidateScript with a static parameter lets you define an arbitrary PowerShell code block to validate a parameter value, it does not allow you to see values of other static parameters. See the section below titled “Example of [ValidateScript()]’s Shortcoming” for a real-world example of how I ran into this issue.

Drawbacks

As great as dynamic parameters can be, you should exercise some caution when using them. First of all, dynamic parameters simply take more effort to implement than static parameters. As a side effect of this, they are also less structured, which makes code maintenance a bit harder and more confusing. The parameter declaration itself, along with any attributes you want to apply to the parameter, must be explicitly created using the New-Object cmdlet, or compatible .NET APIs.

More importantly though, besides the fact that they’re more complicated to write, they are not discoverable in the same way that static parameters are. Given that PowerShell is all about discovery (among other things), this is an important point. Static parameters are easily discovered, as they can be easily analyzed (“reflected” to use proper .NET terminology) by the PowerShell engine prior to execution time.

Example of [ValidateScript()]’s Shortcoming

I’ll describe a situation to you, where the ability of dynamic parameters to read static parameter values during validation is important. I was writing an advanced function that takes two parameters: a WMI namespace (eg. root\cimv2), and a computer name (eg. Gandalf.mydomain.loc). My goal was to validate the existence of the WMI namespace in the parameter declaration, so that I wouldn’t have to worry about checking for its existence later on in the function body. Initially, not knowing any better, I assumed that I could use the ValidateScript attribute on the –Namespace parameter, and build a WMI path including the –ComputerName property (eg. \\$ComputerName\$Namespace:__thisNAMESPACE).

        [ValidateScript({
            if (([wmiclass]"\\${ComputerName}\root\cimv2:__ThisNamespace").__namespace -eq $_)
            {
                return $true
            }
            else
            {
                return $false
            }
        })]
        ${Namespace} = 'root\subscription'

Note: If no value is passed to a parameter, and the parameter’s default value is used, the code block in [ValidateScript()] will not execute.

I quickly found out that this wouldn’t work, because the $ComputerName parameter was not available in the ValidateScript code block. Only the current parameter was accessible using the $_ automatic variable. With this in mind, I went ahead and moved the namespace validation into the BEGIN { … } block of my function — this was easy enough, but not quite how I wanted to structure it either. It was then that I remembered that dynamic parameters could read other parameter’s values, and went back to have a read about them.

Unfortunately I don’t have an example of using dynamic parameters, since I elected to stick with validating in the function body. The main reason I chose to go that route, was because I didn’t want to lose the discoverability of parameters on this function, or the other functions I’m writing that are similar to it. If you would like to better understand dynamic parameters, please see the links in the references section below.

Conclusion

As covered in this article, we have shown how parameter validation via the [ValidateScript()] attribute is both powerful and limited. We have also explored how the use of more flexible dynamic parameters can help us overcome these challenges, with some sacrifice in discoverability and complexity.

References

Posted in powershell, scripting, wmi | Tagged: , , , , , , , , , , , , | Leave a Comment »