Getting error when connecting two Azure Analysis Services from child scripts - powershell

I am working on PowerShell scripts. I have two scripts in those scripts I am connecting with two Azure Analysis Servers one by one. And these scripts are calling by the main script. I am getting errors
"Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an object."
My Scripts code is below
Child1.ps1
param(
[String]
$envName1,
[String]
$toBeDisconnect1
)
$loadInfo1 = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
$server1 = New-Object Microsoft.AnalysisServices.Server
if($toBeDisconnect1 -eq "No")
{
$server1.Connect($envName1)
return $server1
}
elseif($toBeDisconnect1 -eq "Yes")
{
$server1.Disconnect()
Write-Host $server1 " has been disconnected."
}
Child2.ps1
param(
[String]
$envName1,
[String]
$toBeDisconnect1
)
$loadInfo1 = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
$server1 = New-Object Microsoft.AnalysisServices.Server
if($toBeDisconnect1 -eq "No")
{
$server1.Connect($envName1)
return $server1
}
elseif($toBeDisconnect1 -eq "Yes")
{
$server1.Disconnect()
Write-Host $server1 " has been disconnected."
}
MainParent.ps1
param(
$filePath = "C:\Users\user1\Desktop\DBList.txt"
)
$command = "C:\Users\User1\Desktop\test1.ps1 –envName1
asazure://aspaaseastus2.asazure.windows.net/mydevaas -toBeDisconnect1 No"
$Obj1 = Invoke-Expression $command
Start-Sleep -Seconds 15
$command1 = "C:\Users\User1\Desktop\test2.ps1 –envName2
asazure://aspaaseastus2.asazure.windows.net/myuataas -toBeDisconnect2 No"
$Obj2 = Invoke-Expression $command1
Error is below in MainParent.ps1
Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an
object."
At C:\Users\User1\Desktop\test1.ps1:13 char:5
+ $server1.Connect($envName1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NullReferenceException
Exception calling "Connect" with "1" argument(s): "Object reference not set to an instance of an
object."
At C:\Users\User1\Desktop\test2.ps1:13 char:5
+ $server2.Connect($envName2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NullReferenceException
My AzureAnalysis Services dll version is 13.0.4495.10. I am sharing this info, may be it can be a issue.

It seems Invoke-Expression is not passing the param envName1. I would rather call the child script normally like below:
$Obj1 = &"C:\Users\User1\Desktop\test1.ps1" -envName1 "asazure://aspaaseastus2.asazure.windows.net/mydevaas" -toBeDisconnect1 No | select -Last 1
$Obj2 = &"C:\Users\User1\Desktop\test2.ps1" -envName2 "asazure://aspaaseastus2.asazure.windows.net/myuataas" -toBeDisconnect2 No | select -Last 1

$command = "C:\Users\User1\Desktop\test1.ps1 –envName1 asazure://aspaaseastus2.asazure.windows.net/mydevaas -toBeDisconnect1 No"
Look closely at the hyphen before envName1. It's actually – (en-dash) instead of - (hyphen). That's the reason envName1 is not being passed. The second param toBeDisconnect1 has it correct.
How to know the difference? - (hyphen) is shorter than – (en-dash) :)

Related

Upload file to SharePoint

I would like to create a program in PowerShell to upload files to Sharepoint on schedule (using Task Scheduler)
I was looking for solution and I found this interesting article.
Based on this I wrote this script below:
Import-Module Microsoft.Online.Sharepoint.Powershell -DisableNameChecking;
(System.Reflection.Assembly)::LoadWithPartialName("System.IO.MemoryStream")
Clear-Host
$cred = Get-Credential "emailaddress#domain.com"
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($cred.Username, $cred.Password)
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext("https://")
$clientCOntext.Credentials = $credentials
if (!$clientContext.ServerObjectIsNull.Value) {Write-host "Connected to site" -ForegroundColor Green}
Function UploadFileToLibrary(){
$docLib - $clientContext.Web.Lists.GetByTitle("IT Documents");
$clientContext.Load($docLib);
$clientContext.ExecuteQuery();
$rootFolder = $docLib.RootFolder
$Folder = "\\10.x.x.x\xpbuild$\IT\Level0\scresult\Upload";
$FilesInRoot = Get-ChildItem - Path $Folder | ? {$_.psIsContainer -eq $False}
Foreach ($File in ($FilesInRoot))
{
$startDTM = (Get-Date)
}Write-Host "Uploading File" $File.Name "to" $docLib.Title -ForegroundColor Blue
UploadFile $rootFolder $File $false
$endDTM = (Get-Date)
Write-Host "Total Elapsed Time : $(($endDTM-$startDTM).totalseconds) seconds"
}
Function UploadFile ($SPListFolder, $File, $CheckInRequired){
$FileStream = New-Object IO.FileStream($File.FullName,[System.IO.FileMode]::Open)
$FileCreationInfo = New-Object Microsoft.SharePoint.Client.FileCreationInformation
$FileCreationInfo.Overwrite = $True
$FileCreationInfo.ContentStream = $FileStream
$FileCreationInfo.Url = $File
$UploadedFile = $SPListFolder.Files.Add($FileCreationInfo)
If($CheckInRequired){
$clientContext.Load($UploadedFile)
$clientContext.ExecuteQuery()
If($uploadedFile.CheckOutType -ne "none"){
$UploadedFile.CheckIn("Checked in by Administrator", [Microsoft.SharePoint.Client.CheckinType]::MajorCheckIn)
}
}
$clientContext.Load($UploadedFile)
$clientContext.ExecuteQuery()
}
UploadFileToLibrary
When I tried to execute this I see that connection is active but I got an error:
Method invocation failed because [Microsoft.SharePoint.Client.List] does not contain a method named 'op_Subtraction'.
At C:\PowerShell\UploadSharepoint.ps1:11 char:1
+ $docLib - $clientContext.Web.Lists.GetByTitle("IT Documents");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Subtraction:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Cannot find an overload for "Load" and the argument count: "1".
At C:\PowerShell\UploadSharepoint.ps1:12 char:1
+ $clientContext.Load($docLib);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Exception calling "ExecuteQuery" with "0" argument(s): "List 'IT Documents' does not exist at site with URL 'https://'."
At C:\PowerShell\UploadSharepoint.ps1:13 char:1
+ $clientContext.ExecuteQuery();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ServerException
I cannot tell if any new problem occur once you fix that one, but given that the 2nd and 3rd error is caused by incorrect assignment of $docLib variable, changing - to =should resolve these three:
HERE |
$docLib = $clientContext.Web.Lists.GetByTitle("IT Documents");
# Below uses $docLib so it cannot be executed properly unless $docLib is assigned correct value
$clientContext.Load($docLib);
# And below fails as the above is not executed successfully
$clientContext.ExecuteQuery();

PowerShell module not dot-sourcing / importing functions as expected

Update 1:
Originally, I posted this with the title: "Scripts ignoring error handling in PowerShell module" as that is the current issue, however it seems more of a module issue, so I have renamed the title.
Update 2:
After a comment that made me question Azure cmdlets, I've tested with the most basic of scripts (added to the module) and the findings are the same, in that the error is not passed to the calling script, however, adding -errorVariable to Get-Service does return something (other than WriteErrorException) that I could probably harness in the handling of the error:
function Test-MyError($Variable)
{
Try
{
Get-Service -Name $variable -ErrorAction Stop -ErrorVariable bar
#Get-AzureRmSubscription -SubscriptionName $variable -ErrorAction Stop
}
Catch
{
Write-Error $error[0]
$bar
}
}
returns:
Test-MyError "Foo"
Test-MyError : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'foo'.
However, if I run "Test-MyError" in ISE, then call the function, I get:
Test-MyError "Foo"
Test-MyError : Cannot find any service with service name 'Foo'.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'Foo'.
So I am not sure what is happening when running "Test-MyError" in ISE and calling it, against it being dot-sourced in the PSM1 file and then calling it?
Do I now have to use -ErrorVariable and handle on that?
Original Question:
I have two functions in a module: Get-Subscription and Get-AllSubscriptions. Each function sits in its own PS1 file and the PSM1 file dot-sources them. The module seems fine as the module scripts are accessible using intelisense and the module loads without issue. I've used this structure in many modules and I haven't come across this problem before. (Although I wonder if MS have changed the way modules work in PS 5.1 as I have noticed using FunctionsToExport='x','y','Z' and Export-ModuleMember don't seem to behave the same way as they used to.)
Get-AllSubscriptions calls Get-Subscription.
If I am not logged into Azure, Get-Subscription should throw an error which is handled, prompting me to log in. This works as expected, if I call Get-Subscription from the Get-Subscription.ps1.
However, when I call Get-Subscription from the a new PS1 file, Get-AllSubscriptions or from the powershell console, it doesn't work. It iterates all the way through the do..until loop, without "handling" the errors as I would expect. On each iteration, it seems to throw a generic error:
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
However, I do see the last error, Get-Subscription : Unable to find requested subscription after 3 login attempts.
If I execute Get-Subscription in ISE, then call Get-Subscription in a new PS1 file or from Get-AllSubscriptions, it works as expected, however, once I re-import the module (Import-Module AzureVnetTools -Force -Verbose), it goes back to the incorrect behaviour.
If I dot-source Get-Subscription, inside the caller script, it works, but why? This is what should happen with the module's PSM1.
Can anyone help me work out what I am doing wrong here?
(PS 5.1, Windows 7)
Get-Subscription:
function Get-Subscription
{
[cmdletbinding()]
Param
(
[string]$SubscriptionName,
[string]$UserName,
[string]$code
)
$c=1
Write-Verbose "Checking access to '$SubscriptionName' with user '$UserName'..."
Do
{
Write-Verbose "Attempt $c"
Try
{
$oSubscription = Get-AzureRmSubscription -SubscriptionName $SubscriptionName -ErrorAction Stop -WarningAction SilentlyContinue
Write-Verbose "Subscription found: $($oSubscription.SubscriptionName)."
}
Catch
{
if($error[0].Exception.Message -like "*Please verify that the subscription exists in this tenant*")
{
Write-Verbose "Cannot find subscription '$SubscriptionName' with provided credentials."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
elseif($error[0].Exception.Message -like "*Run Login-AzureRmAccount to login*")
{
Write-Verbose "No logged in session found. Please log in."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
else
{
Write-Error $error[0]
}
}
$c++
}
until(($oSubscription) -or ($c -eq 4))
if($c -eq 4)
{
Write-Error "Unable to find requested subscription after $($c-1) login attempts."
break
}
$oSubscription | Add-Member -MemberType NoteProperty -Name Code -Value $code
$oSubscription
}
Get-AllSubscriptions:
function Get-AllSubscriptions
{
[cmdletbinding()]
param
(
[string]$MasterSubscription,
[string]$MasterSubscriptionCode,
[string]$MasterSubscriptionUsername,
[string]$ChildSubscription,
[string]$ChildSubscriptionCode,
[string]$ChildSubscriptionUsername
)
Write-Verbose "Getting all subscriptions..."
$oAllSubscriptions = #()
$oMasterSubscription = Get-Subscription -SubscriptionName $MasterSubscription -UserName $MasterSubscriptionUsername -code $MasterSubscriptionCode -Verbose
$oChildSubscription = Get-Subscription -SubscriptionName $ChildSubscription -UserName $ChildSubscriptionUsername -code $ChildSubscriptionCode -Verbose
$oAllSubscriptions = ($oMasterSubscription,$oChildSubscription)
$oAllSubscriptions
}
Test:
$splat2 = #{
SubscriptionName = "SomeSubscription"
Code = "S02"
Username = "some.user#somewhere.com"
}
#Write-Output "Dot-source:"
#. "D:\Temp\PS.Modules\AzureVnetTools\functions\public\Get-Subscription.ps1"
Get-Subscription #splat2 -verbose
Output:
Get-Subscription #splat2 -verbose
VERBOSE: Checking access to 'SomeSubscription' with user 'some.user#somewhere.com'...
VERBOSE: Attempt 1
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 2
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 3
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
Get-Subscription : Unable to find requested subscription after 3 login attempts.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
AzureVnetTools.psm1
#Get public and private function definition files.
$Public = #( Get-ChildItem -Path $PSScriptRoot\Functions\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = #( Get-ChildItem -Path $PSScriptRoot\Functions\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in #($Public + $Private))
{
#write-error $import.fullname
Try
{
#Write-Host "Dot-sourcing file: $($import.fullname)."
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
Export-ModuleMember -Function $Public.Basename
AzureVnetTools.psd1 (Relevant section):
FunctionsToExport = '*'
-ErrorAction Stop -WarningAction SilentlyContinue
Is it a warning that's being thrown instead of an error?
So my specific problem was that I was relying on handling the error and doing something based on that. The problem was caused by the way PowerShell's Write-Error works (or not) as I learned from the reply here, given by #Alek.
It simply wasn't passing the actual error back to the calling script. As #Alex suggested, I replaced Write-Error with $PSCmdlet.WriteError(). Although this didn't totally work.
In the Catch{} block, I then changed $error[0] to $_ and the full error was returned to the calling script / function.
I went one further and wrote a reusable function, added to my module:
function Write-PsError
{
[cmdletbinding()]
Param
(
[Exception]$Message,
[Management.Automation.ErrorCategory]$ErrorCategory = "NotSpecified"
)
$arguments = #(
$Message
$null #errorid
[Management.Automation.ErrorCategory]::$ErrorCategory
$null
)
$ErrorRecord = New-Object -TypeName "Management.Automation.ErrorRecord" -ArgumentList $arguments
$PSCmdlet.WriteError($ErrorRecord)
}
Which seems to be working well at the moment. I especially like the way intellisense picks up all the ErrorCategories. Not sure what or how ISE (PS 5.1 / Win 7) does that. I thought I was going to have to add my own dynamic parameter.
HTH.

Powershell won't read header text in word documents?

I am in need of checkingh a larger number of word documents (doc & docx) for a specific text and found a great tutorial and script by the Scripting Guys;
https://blogs.technet.microsoft.com/heyscriptingguy/2012/08/01/find-all-word-documents-that-contain-a-specific-phrase/
The script reads all documents in a directory and gives the following output;
Number of times mentioned
Total word count in all documents where the specific text is found
The directory of all files containing the specific text.
This is all I need, however their code doesn't seem to actually check the headers of any document, which incidentally is where the specific text I'm looking for is located. Any tips & tricks in making the script read header text would make me very happy.
An alternative solution might be to remove the formatting so that the header text becomes part of the rest of the document? Is this possible?
Edit: Forgot to link the script:
[cmdletBinding()]
Param(
$Path = "C:\Users\use\Desktop\"
) #end param
$matchCase = $false
$matchWholeWord = $true
$matchWildCards = $false
$matchSoundsLike = $false
$matchAllWordForms = $false
$forward = $true
$wrap = 1
$application = New-Object -comobject word.application
$application.visible = $False
$docs = Get-childitem -path $Path -Recurse -Include *.docx
$findText = "specific text"
$i = 1
$totalwords = 0
$totaldocs = 0
Foreach ($doc in $docs)
{
Write-Progress -Activity "Processing files" -status "Processing $($doc.FullName)" -PercentComplete ($i /$docs.Count * 100)
$document = $application.documents.open($doc.FullName)
$range = $document.content
$null = $range.movestart()
$wordFound = $range.find.execute($findText,$matchCase,
$matchWholeWord,$matchWildCards,$matchSoundsLike,
$matchAllWordForms,$forward,$wrap)
if($wordFound)
{
$doc.fullname
$document.Words.count
$totaldocs ++
$totalwords += $document.Words.count
} #end if $wordFound
$document.close()
$i++
} #end foreach $doc
$application.quit()
"There are $totaldocs and $($totalwords.tostring('N')) words"
#clean up stuff
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($range) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
EDIT 2: My colleague got the idea to call on the section header instead;
Foreach ($doc in $docs)
{
Write-Progress -Activity "Processing files" -status "Processing $($doc.FullName)" -PercentComplete ($i /$docs.Count * 100)
$document = $application.documents.open($doc.FullName)
# Load first section of the document
$section = $doc.sections.item(1);
# Load header
$header = $section.headers.Item(1);
# Set the range to be searched to only Header
$range = $header.content
$null = $range.movestart()
$wordFound = $range.find.execute($findText,$matchCase,
$matchWholeWord,$matchWildCards,$matchSoundsLike,
$matchAllWordForms,$forward,$wrap,$Format)
if($wordFound) [script continues as above]
But this is met with the following errors:
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\count_mod.ps1:27 char:31
+ $section = $doc.sections.item <<<< (1);
+ CategoryInfo : InvalidOperation: (item:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\count_mod.ps1:29 char:33
+ $header = $section.headers.Item <<<< (1);
+ CategoryInfo : InvalidOperation: (Item:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\count_mod.ps1:33 char:26
+ $null = $range.movestart <<<< ()
+ CategoryInfo : InvalidOperation: (movestart:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\count_mod.ps1:35 char:34
+ $wordFound = $range.find.execute <<<< ($findText,$matchCase,
+ CategoryInfo : InvalidOperation: (execute:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Is this the right way to go or is it a dead end?
if you want the header text, you can try the following:
$document.content.Sections.First.Headers.Item(1).range.text
For anyone looking at this question in the future: Something isn't quite working with my code above. It seems to return a false positive and puts $wordFound = 1 regardless of the content of the document thus listing all documents found under $path.
Editing the variables within Find.Execute doesn't seem to change the outcome of $wordFound. I believe the problem might be found in my $range, as it is the only place I get errors in while going through the code step by step.
Errors listed;
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\Powershell\count.ps1:24 char:58
+ $range = $document.content.Structures.First.Headers.Item <<<< (1).range.Text
+ CategoryInfo : InvalidOperation: (Item:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Exception calling "MoveStart" with "0" argument(s): "The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)"
At C:\Users\user\Desktop\Powershell\count.ps1:25 char:26
+ $null = $range.MoveStart <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodCOMException
You cannot call a method on a null-valued expression.
At C:\Users\user\Desktop\Powershell\count.ps1:26 char:34
+ $wordFound = $range.Find.Execute <<<< ($findText,$matchCase,
+ CategoryInfo : InvalidOperation: (Execute:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

Run remote PowerShell Office uninstallation script

KB2956128 is causing headache for users in my network. I do not run WSUS in this environment so I was going to employ PS script to take care of uninstallation.
By all means the script below should work
$comp = 'PC03'
$scrblock =
{
$TitlePattern = 'KB2956128'
$Session = New-Object -ComObject Microsoft.Update.Session
$Collection = New-Object -ComObject Microsoft.Update.UpdateColl
$Installer = $Session.CreateUpdateInstaller()
$Searcher = $Session.CreateUpdateSearcher()
$Searcher.QueryHistory(0, $Searcher.GetTotalHistoryCount()) |
Where-Object { $_.Title -match $TitlePattern } |
ForEach-Object {
Write-Verbose "Found update history entry $($_.Title)"
$SearchResult = $Searcher.Search("UpdateID='$($_.UpdateIdentity.UpdateID)' and RevisionNumber=$($_.UpdateIdentity.RevisionNumber)")
Write-Verbose "Found $($SearchResult.Updates.Count) update entries"
if ($SearchResult.Updates.Count -gt 0) {
$Installer.Updates = $SearchResult.Updates
$Installer.Uninstall()
$Installer | Select-Object -Property ResultCode, RebootRequired, Exception
# result codes: http://technet.microsoft.com/en-us/library/cc720442(WS.10).aspx
}
}
}
Invoke-Command -ComputerName $comp -ScriptBlock $scrblock -Credential 'myDomain\administrator'
Instead I get this error
Exception calling "CreateUpdateInstaller" with "0" argument(s): "Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
+ PSComputerName : PC03
Exception calling "QueryHistory" with "2" argument(s): "Exception from HRESULT: 0x80240007"
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
+ PSComputerName : PC03
I don't quite understand why the access is denied. Any ideas?
Short answer is that the ComObject does not allow the CreateUpdateInstaller to be called remotely. You can only do this locally, not over sessions or any other remoting. You can however use psexec to remotely execute your script as system.

Catching dynamic exception types in PowerShell

I'm working on a PowerShell library that automates some network management operations. Some of these operations have arbitrary delays, and each can fail in unique ways. To handle these delays gracefully, I'm creating a generic retry function that has three main purposes:
Execute an arbitrary command (with parameters)
If it fails in a recognized way, try it again, up to some limit
If it fails in an unexpected way, bail and report
The problem is item #2. I want to be able to specify the expected exception type for the command. How can I do this in PowerShell?
Here's my function:
Function Retry-Command {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, Position=0)]
[String] $name,
[Parameter(Mandatory=$True, Position=1)]
[String] $scriptBlock,
[String[]] $argumentList,
[Int] $maxAttempts=3,
[Int] $retrySeconds=10,
[System.Exception] $retryException=[System.Management.Automation.RuntimeException]
)
$attempts = 1
$keepTrying = $True
$cmd = [ScriptBlock]::Create($scriptblock)
do {
try {
&$cmd #argumentList
$keepTrying = $False
Write-Verbose "Command [$commandName] succeeded after $attmpts attempts."
} catch [$retryException] {
$msg = "Command [$commandName] failed. Attempt $attempts of $maxAttempts."
Write-Verbose $msg;
if ($maxAttempts -gt $attempts) {
Write-Debug "Sleeping $retrySeconds"
Start-Sleep -Seconds $retrySeconds
} else {
$keepTrying = $False
Write-Debug "Reached $attempts attempts. Re-raising exception."
Throw $_.Exception
}
} catch [System.Exception] {
$keepTrying = $False
$msg = "Unexpected exception while executing command [$CommandName]: "
Write-Error $msg + $_.Exception.ToString()
Throw $_.Exception
} finally {
$attempts += 1
}
} while ($True -eq $keepTrying)
}
I call it like this:
$result = Retry-Command -Name = "Foo the bar" -ScriptBlock $cmd -ArgumentList $cmdArgs
But this is the result:
Retry-Command : Cannot process argument transformation on parameter 'retryException'.
Cannot convert the "System.Management.Automation.RuntimeException" value of type "System.RuntimeType" to type "System.Exception".
At Foo.ps1:111 char:11
+ $result = Retry-Command <<<< -Name "Foo the bar" -ScriptBlock $cmd -ArgumentList $cmdArgs
+ CategoryInfo : InvalidData: (:) [Retry-Command], ParameterBindin...mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Retry-Command
This seems to be saying that the type of [System.Management.Automation.RuntimeException] is not itself a [System.Exception], but is instead a [System.RuntimeType] which makes sense.
So, how do I specify the type of the exception to be caught?
It's not possible to use a variable as a catch criteria, it has to be a type-object (or something), everything else gives you an error. A workaround would be something like this:
#You can get the name of the exception using the following (or .Name for just the short name)
#PS > $myerr.Exception.GetType().Fullname
#System.UnauthorizedAccessException
function test {
param(
#Validate that specified name is a class that inherits from System.Exception base class
[ValidateScript({[System.Exception].IsAssignableFrom([type]$_)})]
$ExceptionType
)
try {
#Test-script, Will throw UnauthorizedAccessException when not run as admin
(Get-Content C:\test.txt) | % { $_ -replace 'test','lol' } | Set-Content C:\test.txt
}
catch [System.Exception] {
#Check if exceptiontype is equal to the value specified in exceptiontype parameter
if($_.Exception.GetType() -eq ([type]$ExceptionType)) {
"Hello. You caught me"
} else {
"Uncaught stuff: $($_.Exception.Gettype())"
}
}
}
A few tests. One with non-existing type, then with non-exception type, and finally a working one
PS > test -ExceptionType system.unaut
test : Cannot validate argument on parameter 'ExceptionType'. Cannot convert the "system.unaut" val
ue of type "System.String" to type "System.Type".
At line:1 char:21
+ test -ExceptionType system.unaut
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,test
PS > test -ExceptionType String
test : Cannot validate argument on parameter 'ExceptionType'. The "[System.Exception].IsAssignableF
rom([type]$_)" validation script for the argument with value "String" did not return true. Determin
e why the validation script failed and then try the command again.
At line:1 char:21
+ test -ExceptionType String
+ ~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,test
PS > test -ExceptionType UnauthorizedAccessException
Hello. You caught me