Tag Archives: DateTime

Bulk Remove Variables, Carefully

Ever have one of those situations where you need to remove, or perhaps reinitialize, a set of variables and have to deal with remembering them all? This doesn’t happen often, but there are times when I need to clear, or remove, variables between loop iterations. You either need to keep track of all your variable names, or — and I wouldn’t recommend this so much — do a Remove-Variable -Name *. While the help on PowerShell 5.1.14394.1000 (5.1 preview version), says that Remove-Variable doesn’t accept wildcards, it does, and I think it always has.

Let’s start with the below example. These few lines do the following: 1. Return the number of variables in the PowerShell session, 2. Add a new variable x, 3. Return the number of variables again, 4. Remove all the variables I can (some can’t be removed), 5. Return the number of variables again (again), and 6. See if I can return my variable x.

[tommymaynard@srv01 c/~]$ (Get-Variable).Count
51
[tommymaynard@srv01 c/~]$ New-Variable -Name x -Value 'string'
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
52
[tommymaynard@srv01 c/~]$ Remove-Variable -Name * -ErrorAction SilentlyContinue
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
29
[tommymaynard@srv01 c/~]$ Get-Variable -Name x
Get-Variable : Cannot find a variable with the name 'x'...

While we can do this to remove the variables we’ve created in the session, we end up removing variables that weren’t necessary to remove. We’re better than this.

In the next example, we’ll add a variable prefix “dog” to all of our variables. This will allow us to find all of the variables by prefix, and then delete just those. Not a bad idea really, but it wasn’t my first thought.

[tommymaynard@srv01 c/~]$ New-Variable -Name dog1 -Value 'string1'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog2 -Value 'string2'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog3 -Value 'string3'
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*

Name                           Value
----                           -----
dog1                           string1
dog2                           string2
dog3                           string3

[tommymaynard@srv01 c/~]$ Remove-Variable -Name dog*
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*
[tommymaynard@srv01 c/~]$ # No results, as they've been deleted.

Had I thought of the above option first, I might’ve just gone with that idea, but let me share what I was really thinking. It’s involves the description property. When we create our variables with New-Variable, we have the option to set more than just the Name and Value. Let’s take a look at the below example, and then discuss it.

[tommymaynard@srv01 c/~]$ New-Variable -Name a -Value 'stringA' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ $StartDate = Get-Date
[tommymaynard@srv01 c/~]$ New-Variable -Name b -Value 'stringB' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ New-Variable -Name c -Value 'stringC' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c

Name                           Value
----                           -----
a                              stringA
b                              stringB
c                              stringC

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Select-Object Name,Value,Description

Name Value   Description
---- -----   -----------
a    stringA 11/03/2016 20:52:41
b    stringB 11/03/2016 20:53:05
c    stringc 11/03/2016 20:53:20

Now, it’s important to remember that a variable’s description property is a string. This means that when we put the current date in the property, it was converted to a string. That means that Thursday, November 3, 2016 20:53:20 PM became this: 11/03/2016 20:53:20. What we’ll need to do is convert it back to a datetime before we compare. We do that in the below example by casting the description property as a datetime object. If it’s not clear yet, our end goal is to remove variables that were created after a specific point in time.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {[datetime]$_.Description -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

We can also use the Get-Date cmdlet to do this, as well.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

Since we can now isolate the variables we created after a specific time, we can go ahead and blow them away. Here goes:

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate} | Remove-Variable
[tommymaynard@srv01 c/~]$ Get-Variable a,b,c

Name                           Value
----                           -----
a                              stringA
Get-Variable : Cannot find a variable with the name 'b'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (b:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Get-Variable : Cannot find a variable with the name 'c'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (c:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Well, that’s it for today. Now you can use description property of your variables to hold the time in which they were created. This will allow you to selective remove any of them based on a start time. I like it, although the whole prefix thing might be an easier option.

Converting to UTC Time (Thanks, SharePoint)

Earlier this year, I created and implemented a constrained, PowerShell endpoint on two SharePoint Central Admin servers. The purpose was to allow non-administrative users the ability to remotely connect via PSRemoting, or PowerShell Web Access, to create new SharePoint Site Collections (SCs). By default, I provided a few Get- SharePoint cmdlets, as well as, the New-SPSite SharePoint cmdlet. A month or so after requesting and then receiving the Set-SPSite cmdlet, the team using the endpoint, requested another cmdlet: Remove-SPSite – a cmdlet that made me a little leery. While I trusted the people who would use the cmdlet, I needed to sleep at night, and so I needed to add some precautions.

So, what to do? I created a “proxy” function in place of the SharePoint Remove-SPSite cmdlet. While the cmdlet wouldn’t be available, the function would be. The best part about writing your own function, for inclusion in a constrained endpoint, has to be the ability to make use of cmdlets in the function, that are not available in the endpoint. The Remove-SPSite cmdlet is used in my function, even when it isn’t available to the people that use the endpoint. Simply, badass.

While I’m not going to share the complete function, I will explain the logic I decided to use. I allowed people using the endpoint, the ability to delete SCs under one condition – that the SC had been created within the last six hours. This helped protect any SCs that had been created prior to six hours ago. To do this, I decided I would simply get the current date and compare it to the created date of the SC – small problem.

First, let’s return the SC that I created.

PS C:\> Get-SPSite -Identity https://teamsite.sp.mydomain.com

Url                                                     CompatibilityLevel
---                                                     ------------------
https:/teamsite.sp.mydomain.com                         15

Now, let’s return the time when the SC was created. The problem, in the example below, is that I didn’t create the site at 2 a.m. From my point in time, that was 7 hours in the future. With the help of a coworker, I realized that the time being used by SharePoint on the back end, was Coordinated Universal Time, or UTC, regardless that all the other SharePoint times were set for my time zone.

PS C:\> (Get-SPSite -Identity https://teamsite.sp.mydomain.com).RootWeb.Created

Monday, November 17, 2014 2:05:10 AM

My first thought was that this wasn’t going to be an effective way to protect the SCs, until I remembered, I can convert the current time. The easiest way to do this was to use the DateTime object’s method, .ToUniversalTime(). That’s right, take the current time, convert it to Universal Time, and then use that for comparison.

In the example below, we do several things. The first three lines set some variables. In line 1, we capture the SC to a variable. In line 2, we create the variable that holds the numeric value of hours we are willing to allow, and in line 3 we create the DateTime object in UTC that we use to compare against the SharePoint SC.

$SPSite = Get-SPSite -Identity https://teamsite.sp.mydomain.com
$Hours = 6
$TimeHoursAgo = ((Get-Date).ToUniversalTime()).AddHours(-$Hours)

If ($SPSite.RootWeb.Created -gt $TimeHoursAgo) {
    Remove-SPSite -Identity $SPSite -Confirm:$True
} Else {
    Write-Warning -Verbose "Unable to delete Site Collection: $($SPSite.Url) (Over $Hours hours old))."
}

The rest of the example does the comparison between the DateTime object we’ve created and the created date of the SC. If the SC was created after our $TimeHoursAgo variable (meaning that it is greater than $TimeHoursAgo), then it can be deleted, and if it wasn’t, then it cannot be deleted.

Notice the .AddHours() method, above, is using a negative sign before the $Hours variable – this is used to move back in time. Also, pay attention to the embedded parenthesis as the value $TimeHoursAgo is being assigned. They indicate the order in which the command is executing, beginning with the most embedded set. You can ignore the parenthesis that are used as a part of the methods, such as .ToUniversalTime(), even if they contain something between the parenthesis. I’ve included an example of what that line would look like had it been written procedurally.

PS C:\> $Hours = 6
PS C:\> $TimeHoursAgo = Get-Date
PS C:\> $TimeHoursAgo = $TimeHoursAgo.ToUniversalTime()
PS C:\> $TimeHoursAgo = $TimeHoursAgo.AddHours(-$Hours)

Keep in mind that various Microsoft technologies may use UTC on the back end, even if the GUI shows your proper time zone.