Add-PSSnapIn Microsoft.SharePoint.PowerShell Failing after SharePoint Install

I have some PowerShell scripts that run PreRequisiteInstaller.exe, Setup.exe, and then configure a farm based on an XML configuration file.  I noticed that if Setup.exe did not prompt for a reboot, that my script would fail with the following error directly after Setup.exe completed.  The line that is failing is the Add-PSSnapIn Microsoft.SharePoint.PowerShell line :

The local farm is not accessible. Cmdlets with FeatureDependencyId are not registered.
No xml configuration files loaded.
The type initializer for 'Microsoft.SharePoint.Utilities.SPUtility' threw an exception.
Add-PSSnapin : The type initializer for 'Microsoft.SharePoint.Utilities.SPUtility' threw an exception.

If I leave the PowerShell instance running, I can reproduce the error over, and over, and over…  The workaround is pretty easy, you open a new instance of PowerShell, and the Add-PSSnapIn command works.  The trouble with that approach is that I’m trying to automate work, and avoiding errors is what it’s all about.  I was also going for a single PowerShell window experience, and that’s not going to work with any workaround to launch multiple processes.  Before resigning to a workaround, I decided to dig into this a bit. I was hoping that an environment variable or registry key was missing that I could fix-up with my script, as a lot of that info typically is read when the process starts.

Since the error mentioned that xml files were not being loaded, I started trying to figure out which XML files are being referred to.  I came across this blog that points out the SharePoint PowerShell DLL loads XML files from the 14\CONFIG\POWERSHELL\Registration directory [as well as some other nifty info about SharePoint working with PowerShell].  With that info, I ran Process Monitor while reproducing the error.  Sure enough, the PowerShell process never even tries to read the XML files when it throws that error.  I can see the process load Microsoft.SharePoint.PowerShell.dll from the GAC, but the process stops doing work after that.  It’s starting to look like the “No xml configuration files loaded” error was right…  :-)

I then break out WinDbg.exe to try and see what is really happening in the process.  I attach to the process, load PSSCor2.dll, and run SXE CLR to break on .Net exceptions.  I reproduce the error, and find an exception is being thrown.  Dumping out the exception, the process is actually having issues loading System.Core.dll [wasn’t expecting that at all]

0:017> !pe 00000000032bb160
Exception object: 00000000032bb160
Exception type: System.IO.FileNotFoundException
Message: Could not load file or assembly 'System.Core, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089' or one of its dependencies. The system cannot find the file specified.
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    000000001D61B0E0 0000000000000001 Microsoft_SharePoint!Microsoft.SharePoint.Utilities.SPReaderWriterLock..ctor(System.String)+0x2
    000000001D61B1B0 000007FF002E73A2 Microsoft_SharePoint!Microsoft.SharePoint.Utilities.SPProcessContext..cctor()+0x32

I look at the top of the managed stack, the snap in has loaded, it’s trying to load the farm, and blowing up cause it can’t find the System.Core assembly.

0:017> !clrstack
OS Thread Id: 0x5bc (17)
Child-SP         RetAddr          Call Site
000000002163dc60 000007ff002e0a7c Microsoft.SharePoint.Administration.SPFarm.FindLocal
000000002163dcf0 000007ff002e02ad Microsoft.SharePoint.Administration.SPWebService.get_ContentService
000000002163dd90 000007feec7f02e4 Microsoft.SharePoint.PowerShell.SPCmdletSnapIn.get_Cmdlets

Process Monitor is not showing the PowerShell process even probing for the DLL, and the module is not loaded into PowerShell already.  The System.Core.DLL is in the GAC using the strong name information in the exception.  I confirm my account has no issues loading the assembly as opening a new instance of PowerShell, with Windbg.exe attached, and running Add-PSSnapIn confirms the modules load without error.

At this point, I decided to just work around the problem, as I was actually trying to get some work done. Maybe if I can’t sleep in the future, I’ll dig into why the process is not trying to load the DLL, but that will have to wait for another day.

My workaround was to have a launcher script.  This script’s purpose was to launch new PowerShell processes, passing in a PS1 file to run.  I already had my scripts split up by functionality, so it wasn’t a lot of work to call the installation PS1 file, then when that script completed, open another PowerShell instance for the configuration scripts. Here’s an example of what that script looks like.

    1: #LaunchScript function will launch a PS1 file with arguments in a new instance
    2: #of PowerShell.
    3: #
    4: #Takes in a Command object which is the following format: 
    5: #
    6: #  "-Command $path\ConfigureFarm.ps1 $Param1 $Param2 $Param3"
    7: #
    8: #LogFilePath  - This is the path to log data to...this should be the same for all the scripts to ensure all the logging is placed in one file.
    9: #
   10: #Title  -  This is the title of the error log that is generated for the redirected error output.
   12: function LaunchScript
   13: {
   14:     param($command, $logFilePath, $title)
   17:     #Build out the path to an error log file, and create the file if it doesn't exist.
   18:     $errorLog = $LogFolderPath + "\" + $title.Replace(" ", "_") + "_" + ($env:ComputerName) + "_Errors.log"
   19:     $errorLogExists = Get-Item $errorLog -ErrorAction SilentlyContinue
   20:     if($errorLogExists -eq $null)
   21:     {
   22:         New-Item $errorLog -ItemType "file" | out-null
   23:     }
   26:     #Target of the command is the PowerShell.exe process
   27:     $TargetPath = "C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe"
   29:     #Call Start-Process and redirect the error stream to the error log file.  Use -wait since we need to wait for the script to complete before completing.  
   30:     Start-Process -FilePath $TargetPath -WorkingDirectory $path -wait -RedirectStandardError $errorLog -ArgumentList $command
   32:     #Check the $errorLog to see if any errors were generated.
   33:     #If there are errors, leave the error log and cancel script processing
   34:     #Otherwise, clean up the error log and continue
   35:     $errorContents = Get-Content $errorLog
   36:     if($errorContents.Length -gt 0)
   37:     {
   38:         Write-Host "End : Errors occurred during processing of $title" -ForegroundColor Red
   39:         Write-Host "Script Processing terminating.  See $errorLog for details." -ForegroundColor Red
   40:         exit
   41:     }
   42:     else
   43:     {
   44:         Write-Host "End : Launching $title"
   45:         Remove-Item $errorLog
   46:     }
   47: }
   50: #Examples of calling LaunchScript for a couple PS1 files.  In this case, both PS1 files take in 4 parameters.
   51: try
   52: {
   53:     #Install PreRequisites and Binaries      
   54:     LaunchScript "-Command $path\InstallSP.ps1 $SchemaLocation $FirstServer $LogFilePath $ScriptCommand" $LogFilePath "Installing SharePoint 2010"
   56:     #Configure the farm
   57:     LaunchScript "-Command $path\ConfigureSP.ps1 $SchemaLocation $FirstServer $LogFilePath $ScriptCommand" $LogFilePath "Configuring Farm"    
   58: }
   59: finally
   60: { 
   61:     #Clean up the Global variables
   62:     Remove-Variable -Name Variable1 -Scope Global
   63:     Remove-Variable -Name Variable2 -Scope Global
   64: }