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=22.214.171.124, 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.
4: #Takes in a Command object which is the following format:
6: # "-Command $path\ConfigureFarm.ps1 $Param1 $Param2 $Param3"
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.
10: #Title - This is the title of the error log that is generated for the redirected error output.
12: function LaunchScript
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)
22: New-Item $errorLog -ItemType "file" | out-null
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)
38: Write-Host "End : Errors occurred during processing of $title" -ForegroundColor Red
39: Write-Host "Script Processing terminating. See $errorLog for details." -ForegroundColor Red
44: Write-Host "End : Launching $title"
45: Remove-Item $errorLog
50: #Examples of calling LaunchScript for a couple PS1 files. In this case, both PS1 files take in 4 parameters.
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"
61: #Clean up the Global variables
62: Remove-Variable -Name Variable1 -Scope Global
63: Remove-Variable -Name Variable2 -Scope Global
nice workaround Jerry. Code is clean. It would be nice to see more scripts from you - have you considered posting some scripts to the TechNet Gallery?
Great job. I am just now testing it in my environment, and it seems to work great!
I was also looking into this issue, but couldn't find a way to get it working within a single powershell instance.