Pseudo Singleton Pattern in PowerShell

1 Oct

I have had a few modules that I have developed where I desired to use the Singleton pattern as I didn’t want multiple instances of a particular object running about across scripts. Building full fledge objects in PowerShell is a bit verbose and not the most straightforward process. I’m also not certain you can properly build an honest Singleton class natively in PowerShell. An alternative to building objects natively in PowerShell is to use C# in line and compile the class with Add-Type. Although that is an acceptable solution and benefits from full enforcement of restricting object creation to calling the Instance method, I wanted to stay native to PowerShell. I’ve adopted the following pattern to accomplish this:

function Get-ObjectInstance{
    param()

    if(Get-Variable -Scope Global -Name 'tmpObject' -EA SilentlyContinue){ 
        return (Get-Variable -Scope Global -Name 'tmpObject').Value 
    }

    $Global:tmpObject = New-Object PSObject
    

   return $tmpObject
}

Using a global variable is required if you want the object available across scripts which is helpful if you want to do things like logging. This concept was initially prompted by an issue I was having with a Logging module I had written. I was invoking a Global logging object in each script which was causing problems on multiple runs. I would call:

# Instatiate Log #
if($GLOBAL:Log -eq $null){
    $GLOBAL:Log = New-LogObject "Log Description"
}

On subsequent runs I would get errors of “Can’t add-member… “ something or other and the logging would error. It required me closing the object properly which was difficult to do cleanly and consistently when creating the Global object in each script as the name could change from script to script if you weren’t careful. Instead of making the variable global in each script, I moved that into a module using the “singleton” pattern as described above in the function Get-ObjectInstance . I initially moved to this method to make it easier to close out the global variable so I wouldn’t run into issues on subsequent runs. The side benefit of this change was that I no longer needed to maintain consistency from script to script for the variable name. Using the pseudo singleton pattern, I could now call:

# Instatiate Log #
$Log = Get-LogInstance "Log Description"

This remove the need for checking if the variable had been created in each script, and I could call scripts in any order knowing I would get a valid and the correct instance of the object I wanted.

Removing a Global Variable – Revisited

14 Nov

I had previously written about an issue that I had with closing out a custom PSObject that caused scripts to fail on subsequent runs. After watching a PluralSight video that covered the concept of passing variables by reference and by value, I re-visited my issue. Initially, I was trying to solve the issue by capturing the variable name and using that name to call:

Remove-Variable -Name 'VariableName'

Although Remove-Variable requires a name to remove the variable, I was going about identifying the name of variable the wrong way. Since the $this variable in the custom PSObject is going to reference the same object as the variable I assigned it to in my scripts I could leverage System.Object.ReferenceEquals() method to identify the variables. My new Close() method for my PSLogging module looks like this:

$tmpVars = Get-Variable -Scope Global | Where-Object{
    [System.Object]::ReferenceEquals($this, $_.Value)}
if($tmpVars.GetType().FullName -eq 'System.Management.Automation.PSVariable'){
    Remove-Variable -Scope Global -Name $tmpVars.Name
}else{
    for($i=0;$i -lt $tmpVars.Count;$i++){
        Remove-Variable -Scope Global -Name $tmpVars[$i].Name
    }
}

I can now reliably identify all references to the log variable and remove them successfully. The concepts of By Value and By Reference weren’t new to me, but I didn’t fully grasp them until watching that video.

Fixing QueryFeatures Error When Looking for Features to Upgrade

26 Jun

UPDATE 11/9/13: Added SourceCode Formatting

PROBLEM:

I was running QueryFeatures on the SPWebApplication to find a SPFeature object to upgrade, but was receiving an error about a deleted site.

An error occurred while enumerating through a collection: The site with the id 8e79d05f-3f0c-4627-8311-7d29b2a2c4a3 could not be found..
At line:1 char:1
+  <<<< (Get-SPWebApplication http://www.devsite.com/).QueryFeatures(‘afda6a62-4389-402f-af19-11bd9ee2b99a’) | ?{$_.Parent.Url -like "http://www.devsite.com/specific/site"}    + CategoryInfo          : InvalidOperation: (Microsoft.Share…esultCollection:SPFeatureQueryResultCollection) [], RuntimeException    + FullyQualifiedErrorId : BadEnumeration

Simon Doy does a good job of explaining the issue in more detail. I had previously created and deleted a site collection for testing purposes and that is when my problems started.

SOLUTION:

To solve the issue I started by running the Get-SPDeletedSite command to find the problem sites. I then removed the deleted sites with Remove-SPDeletedSite. Next, I ran a refresh on the content database to make sure the list of sites was update in the configuration database. The whole PowerShell process looked a little something like this:

PS C:\Users\beavel> Get-SPDeletedSite

WebApplicationId   : 00000000-0000-0000-0000-000000000023 
DatabaseId         : 00000000-0000-0000-0000-000000000024 
SiteSubscriptionId : 00000000-0000-0000-0000-000000000000 
SiteId             : 8e79d05f-3f0c-4627-8311-7d29b2a2c4a3 
Path               : /team/site1 
DeletionTime       : 6/25/2012 8:32:31 PM

WebApplicationId   : 00000000-0000-0000-0000-000000000023 
DatabaseId         : 00000000-0000-0000-0000-000000000024 
SiteSubscriptionId : 00000000-0000-0000-0000-000000000000 
SiteId             : 5fcf2a3b-ee9e-4d27-8e7b-4c7185396dce 
Path               : /team/site2 
DeletionTime       : 6/25/2012 8:18:48 PM


PS C:\Users\beavel> Remove-SPDeletedSite -Identity 5fcf2a3b-ee9e-4d27-8e7b-4c7185396dce

Confirm 
Are you sure you want to perform this action? 
Performing operation "Remove-SPDeletedSite" on Target "/team/site2". 
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help 
(default is "Y"):y 
PS C:\Users\beavel> Get-SPDeletedSite

WebApplicationId   : 00000000-0000-0000-0000-000000000023 
DatabaseId         : 00000000-0000-0000-0000-000000000024 
SiteSubscriptionId : 00000000-0000-0000-0000-000000000000 
SiteId             : 8e79d05f-3f0c-4627-8311-7d29b2a2c4a3 
Path               : /team/site1 
DeletionTime       : 6/25/2012 8:32:31 PM</pre>

<pre>PS C:Usersbeavel> Remove-SPDeletedSite -Identity 8e79d05f-3f0c-4627-8311-7d29b2a2c4a3

Confirm 
Are you sure you want to perform this action? 
Performing operation "Remove-SPDeletedSite" on Target "/team/site2". 
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help 
(default is "Y"): 
PS C:\Users\beavel> $db = Get-SPContentDatabase -Identity 5a83c168-d8e8-44c7-b5d3-46b8d87d9a06 
PS C:\Users\beavel> $db.RefreshSitesInConfigurationDatabase()

I was still running into issues with my original command. To get things working, the “Gradual Site Delete” time job needed to be run for the Web Application that contained the two sites. This was run from Central Admin instead of PowerShell. Once it had completed, I was again able to run my original command. I’m not sure the DB refresh was needed in this process. You may be able to get by without it as I have not tested.

Regex to the Rescue

29 Feb

Problem:

At work, we had a requirement to transform text URL’s into clickable links. Ideally, these links would have been entered in as well formatted hyper links, but that wasn’t the case and we didn’t have the option to implement that fix. Initially, the idea was have end users include the http:// protocol on links so only text would that had http:// would be transformed. This was rather optimistic as end users didn’t follow this training. Users were entering links in all shapes and forms. Some links had no protocol or host. This lead to many links being missed.

Solution:

To solve the problem, I wrote a little regex to capture the links and then reformatted them. The regex is below:

b((https?|ftp)://)?([A-Z|a-z|0-9|-]+[.]){1,4}(com|org|us|net|edu)([A-Z|a-z|./?=_&%|0-9]+)?b

This will match a link with or without a protocol or host specified, an alphanumeric domain with dashes, and a path and query string following it. It only matches on the TLD listed in the middle section. This can be problematic or useful depending on what you want to match. Overall, this was a major improvement that allowed end users to continue with their behavior and still get the result we were looking for. This solution has been rock solid so far in capturing the links entered, but it still has the opportunity to miss certain URL’s. YMMV.

GetJar is logging more than you think

26 Feb

I was doing some network analysis on my phone related to another matter, and noticed that GetJar was logging some of my activity. This isn’t that surprising for an app store that provides free apps. Nothing comes for free. I would expect that they would log some information related to their apps provided through their store. However, what surprised me was logging occurred as I was uninstalling apps that I did not purchase or install through GetJar. After seeing this behavior, GetJar got an immediate uninstall. I don’t know what other data GetJar might have been logging as I didn’t leave it on long enough to find out anymore.

 

Here is what was logged:

GET /backchannel/metadata/
?gjClientInstallationID=<24char string>
&androidID=<44 char string>
&gjClientVerCode=3378
&src=gjca
&gjClientVerName=3.3.78
&packageName=com.qik.android
&status=UNINSTALLED
&versionCode=382
&versionName=0.03.765
&appLabel=Qik
&uninstallTime=1329696276134

 

I have reformatted this GET request for easier reading. The character count is based on the decoded URL. There is nothing super personal in there, but they are definitely collect what apps you are using.

 

I took a quick look at GetJar’s privacy policy to see if this was disclosed. As many privacy policies, the sections on personal information collection are a bit vague and open-ended. Even so, I didn’t get the sense that they would be collecting information on what apps I was uninstalling or using. Here’s the relevant excerpt from their privacy policy:

Personal Information Collected via Technology

As you use the GetJar Site or any GetJar Service, some information may also be collected passively, including your Internet protocol address, browser type, access time, mobile phone model, and telecom carrier. We may also store a small text file called a "Cookie" on your computer or phone to store certain information about your use of the GetJar Site or GetJar Services. We may use both session Cookies (which expire once you close your browser) and persistent Cookies (which stay on your computer or phone until you delete them).

Personal Information from Other Sources

We may receive Personal Information about you from other sources, including other users. We may associate this information with the other Personal Information we have collected about you.

 

 

I went on to take a quick look at their logging server eventlogger.getjar.com. It discloses some configuration information which I am not sure how accurate it is. If the information disclosed is to be trusted, the jetty.config.contextMap seems to give an indication on what else is collected or sent to GetJar.

/*=com.getjar.els.servlet.StatusServlet;
/thrift/*=com.getjar.els.servlet.ThriftServerServlet;
/backchannel/messaging/*=com.getjar.els.servlet.BackchannelMessagingServlet;
/backchannel/usage/*=com.getjar.els.servlet.BackchannelUsageServlet;
/backchannel/metadata/*=com.getjar.els.servlet.BackchannelMetadataServlet;
/backchannel/event/*=com.getjar.els.servlet.BackchannelEventServlet;
/20110506/4933/backchannel/usage/*=com.getjar.els.servlet.BackchannelUsageServlet;
/20111010/5001/backchannel/usage/*=com.getjar.els.servlet.BackchannelUsageServlet;
/20111102/5002/backchannel/usage/*=com.getjar.els.servlet.BackchannelUsageServlet

* Reformatted for easier reading.

It appears that messaging, usage, and event details might be logged as well. What those all entail I’m not sure as uninstalling an app fell under Metadata.

Disclaimer: By writing this, I’m not claiming that GetJar is engaging in malicious activities. If anything, I want others to be aware of this and make an informed decision. No one is being forced to use this app so choose to do what you will.

Removing a Global Variable from the PowerShell environment

26 Jan

UPDATE: It appears that this fix only works when called from the commandline. InvocationPoint.MyCommand.Definition is populated with the script that called it initially. Back to the drawing board for another solution.

Update 11/9/13: Added source code highlighting; typo fixes

Situation:

I had created a custom PowerShell module to use to log activity in other scripts I was writing. The module exposes a function that outputs a custom PSObject to hold the messages. In my scripts, I assigned this PSObject to a global function so it was available to other scripts I might call. At the end of the script, I wanted a method to close or remove the variable the the PSObject was assigned to so that it wouldn’t cause issues on the subsequent runs.

Issue:

The problem I ran into is there isn’t an easy way for removing the variable holding the PSObject. Trying to set $this = $null in the module doesn’t null out the containing object but the $this variable instead. Attempting to remove the variable with the Remove-Variable and $this also failed. My temporary solution was to iterate through all of the NoteProperty’s that were set and $null them out individually. This worked for a while, but required me to close and reopen the ISE between running scripts as the next attempt to run a script caused PowerShell to complain that it couldn’t add to the log variable.

Solution:

I finally tired of having to reopen the ISE and decided to fix my problem properly. The following is my solution:

function Get-VariableName{ 
    param() 
    $tmpRegex = '(?&lt;=$[A-Z,a-z,0-9]*:).*(?=(s=|=))' 
    $this.InvocationPoint.MyCommand.Definition -match $tmpRegex | Out-Null 
    return $Matches[0].Trim() 
}

…

# Close # 
$tmpObject | Add-Member -MemberType ScriptMethod -Name Close ` 
    -Value { 
        param() 
        Remove-Item -Path "Variable:$(Get-VariableName)" 
}

The Get-VariableName uses regex to parse the variable name from the InvocationPoint of $this. The property $this.InvocationPoint.MyCommand.Definition exposes the full command used to create the variable. This was the only place I could find this information exposed in the object itself without adding a property. Now that I had the variable name, I used the Remove-Item function to remove the variable. That is probably the long way of removing instead of just using:

Remove-Variable –Name $(Get-VariableName) –Scope Global

Somehow I overlooked that command when I implemented the fix.

This works for now, but I may need to update the regex at some point to support both global and none global variables as I believe it will fail to capture none global variables. Fortunately I only use this with globally defined variables.

SharePoint 2010, compat.browser, & the Saga of Safari and Chrome

23 Sep

Situation:

Working with a public anonymous site hosted on SharePoint 2010 there was an issue with the mobile view on certain devices. Anybody who has worked with SharePoint knows that by default it will do browser detection for mobile browsers and render a mobile version of the site OOTB. Although this can be helpful with some browsers, most mobile touch screen devices make the need for a specific mobile version unneeded.

The specific issues we were facing are listed below:

Issues:

  1. The files required for the mobile view are located in a directory which was not accessible anonymously causing a 401.
  2. Files that were in the Style Library were not accessible for certain browsers, but worked just fine on other browsers.
  3. Changing the compat.browser file located in inetpubwwwrootwssVirtualDirectories{WebAppFolder}App_Browsers allowed all browsers to see the full site, but the changes caused problems for Safari and Chrome on the desktop as well on mobile devices.

Solution:

For issue #1, we did not want to display the mobile version of the site so the solution was to make sure browser detection stopped rendering the alternative version. This lead to first testing the site by appending ?mobile=0 (http://www.site.com/Pages/default.aspx?mobile=0) to the end of the URL so that SharePoint would skip the browser detection. This revealed issue #2.

Although the mobile view was no longer rendered, the styling on the page was severely off. Users of Windows Phone 7 and desktop browsers were rendering the site just fine, but Android and iPhone devices rendered without proper styling. By looking at IIS logs, I noticed that Android and iPhone devices were getting 401’s when trying to connect to the Style Library. This is where a number of key .js and .css files were stored so it made sense that the site did not render properly. This next part is purely conjecture on my part, but I believe the discrepancy between devices came down to NTLM support. Although the site had anonymous access turned on, the Style Library on a Publishing site does not inherit permissions therefore when anonymous access gets turned on it does receive the anonymous permissions unless directly specified. This can go unnoticed for browsers/OS’s that support NTLM as the Style Library has the group Style Resource Readers on it which contains NT AUTHORITYauthenticated users. My assumption is that browsers that support NTLM use the special user NT AUTHORITYiusr to connect and it is considered authenticated and therefore the site rendered properly for most OS’s and browsers. IIS logs seem to support this as NT AUTHORITYiusr shows up when accessing the Style Library. The solution here was simply to add anonymous access to the Style Library. With this done, appending ?mobile=0 now rendered the site as expected. This left the final issue.

The next step was to turn off browser detection so that no devices were being sent to the mobile experience. From reading various sources, the compat.browser file needed to be edited to set isMobileDevice to false. I wrote a PowerShell script to quickly replace all instances. Find and replace probably would have worked as well, but I like trying new things in PowerShell. With my modified compat.browser file ready, I took and replaced it in our test environment and everything worked as expected. I then moved it to our Production environment and this is where issues started. In Production, the menu system was not being rendered properly on Chrome and Safari. Thinking the issue was related to my change, I reverted to the original file thinking this would fix the problem. It did not. A number of other solutions such as file permissions, editing other compat files, and adding .browser files were attempted but they were unsuccessful at resolving the problem. It wasn’t until we were able to successfully break another system that we stumbled on the answer. Deleting the compat.browser file and then browsing to the site successfully replicated the issue on a dev server. Restoring the file would no longer fix the issue. Server reboots also failed. The final solution turned out to relate to the second file in the App_Browsers directory which is the compat.moss.browser file. When this file was deleted from the directory and the site was visited, the problem went away. The compat.moss.browser file could then be restored without ill effect. My recommendation DON”T DELETE compat.browser when editing. It seems like the “stickiness” of this issue would be considered a bug as one would assume restoring an original file would fix the issue, but it did not. Hope this saves someone else the same headache.