I like using objects in PowerShell, they make management and scripting easier as your are dealing with named sets of information and not having to find objects in numbered arrays or use dictionaries.
That means however that a lot of my scripts start off with a block that looks a little like this:
$UserDefinition = @" public class MyCustomUser { public System.String UserId; public System.String FirstName; public System.String LastName; public System.String Source; public System.Int32 Priority; } "@ Add-Type -TypeDefinition $UserDefinition -Language CSharp $User = New-Object MyCustomUser
Nothing too difficult. I run a lot of scripts interactively, and this never fails (unless I get the syntax wrong inside the code block!)
However, running this exact same block of code as a Scheduled Task, with a specific domain user account failed – and the transcript showed this error:
New-Object : Cannot find type [MyCustomUser]: make sure the assembly containing this type is loaded. At line:1 char:9 + $User = New-Object MyCustomUser + ~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
It turns out that when you run the above code, it generates a temporary file inside the user profile of the user that the task was configured to run as. When you run a script as a scheduled task, the task starts without waiting for the user profile to load. This means that when the Add-Type command is run, it fails. Interestingly, the command itself does not generate an error. It is only when the new object definition is attempted to be used that the command generates the error.
This behaviour is documented in a Microsoft KB article here: https://support.microsoft.com/en-us/kb/2968540. Although this is not specifically describing this error, the circumstances and effects are the same.
With this information I attempted to specify the path to generate the output from Add-Type as a parameter – as below:
Add-Type -TypeDefinition $UserDefinition -Language CSharp -OutputAssembly C:\MyScript\MyCustomUser.dll -OutputType Library
This makes no difference though, and the type still fails to be generated. The temporary files used to generate the output must still be in the profile location.
The only solution that I have made work is to pre-generate the output, and then use Add-Type pointed at the generated DLL file. So, in an interactive session I ran this:
Add-Type -TypeDefinition $UserDefinition -Language CSharp -OutputAssembly C:\MyScript\MyCustomUser.dll -OutputType Library
And then in the script in the scheduled task we changed the import to run this:
Add-Type -Path C:\MyScript\MyCustomUser.dll
The script now runs every time without error. The only annoyance is that if we ever change the definition of the object that we have to regenerate the DLL file. This would be a bigger issue if you were dynamically building custom types and then importing them at runtime.
Hopefully this might help someone else from wondering why their script breaks when run as a scheduled task.
You deserve an oscar for this post. This was a lifesaver.
I found that changing the TMP environment variable to another writable directory before using add-type will solve the problem.
$env:tmp =”path\to\some\writeable\directory”
You have to change the TMP rather than TEMP environment variable as Microsoft programs generally prefer TMP
https://blogs.msdn.microsoft.com/oldnewthing/20150417-00/?p=44213
My previous comment suggesting you can just change the working directory to a writeable directory turned out to be a once off aberration.
WOW, 2 Weeks of knowing exactly the error, but unable to resolve it were fixed by your suggestion of changing the TMP environment variable. Excellent work. Thank you so much!
I ran into a similar problem yesterday on a 2012R2 server, although I was just compiling an enumeration.
Thankfully your post was enough to get me heading in the right direction. All I had to do was make sure the user account had full control of the directory in which the script was started. I initially made the mistake of giving the account full control to the directory containing the script and that also worked so maybe powershell tries a couple of directories. FWIW, I used the -noprofile and -command switches on my command line. I didn’t give it the complete scientific work up, but hope that helps.
Definitely useful info. Next time I’ll try and have a detailed look into that.