What is this command doing? (PowerShell $?) - powershell

Anyone have any idea what this IF command is doing?
Connect-SPConfigurationDatabase -DatabaseName "$configDB" -Passphrase $secPhrase -DatabaseServer "$dbServer" -DatabaseCredentials $dbcreds -ErrorAction SilentlyContinue
If (-not $?)
{
Write-Host -ForegroundColor White " - No existing farm found.`n - Creating config database `"$configDB`"..." }
I'm speculating it's an alternative to :
Try { Do-Something -ea Stop }
Catch { Write-host $_ }
but it's just that, speculation.
I've never seen anything like this before and it's not showing up just the once in a script I'm inheriting. It's throwing me right off!
Thanks

$? is an automatic variable for the error status of the last executed command. See Get-Help about_Automatic_Variables.
Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.

Related

How to write old VB Commands in Powershell

I want to write some old VB into PS, however there are some old DOS commands here that I am struggling to write into powershell. For instance, I want to delete files from an Archive, and the way its written in VB is:
command=(%comspec% /C DEL C:\\MyFile.txt",0,True)
If result <> 0 then
txtFile.writeline "ERROR"
txtFile.writeline "File does not exist.."
result = 0
Else
txtFile.writeline "Success"
End if
In Powershell, The /C DEL is the line I'm having trouble writing. How would I write this command in powershell or would I just ignore it completely and just carry on with my IF statement?
Thanks,
In powershell, just use Remove-Item. This mimics your VB pretty well:
try {
Remove-Item 'C:\MyFile.txt' -Force -ErrorAction 'Stop'
Write-Host 'Success'
} catch [System.Management.Automation.ItemNotFoundException] {
Write-Host 'ERROR'
Write-Host 'File does not exist..'
}
Of course, there's a dozen ways this cat can be skinned:
if (Test-Path 'C:\MyFile.txt') {
Remove-Item 'C:\MyFile.txt' -Force
Write-Host 'Success'
} else {
Write-Host 'ERROR'
Write-Host 'File does not exist..'
}
There's definitely more options out there ...

Catching Cascading Errors in PowerShell

I'm working with a wrapper script that calls a function which queries some databases inside specified servers and inserts metadata to a specific database on a separate server. I use the $error.count variable to determine if the script was successful or not. I anticipate some permission/extraction errors to happen and want these to be caught and ignored (no increase in the $error.count variable but having a warning written in the job log). I can confirm one permission error is happening and is being properly caught. The $error.count variable is not increased but a warning is printed from the catch showing the database that could not be accessed.
My problem occurs after the extraction/insertion function is finished running. Immediately after this function returns to the wrapper script, I have the $error.count variable print again. This time, it returns a 1 as if the error previously caught cascades into the wrapper script. As I mentioned previously, I do not want this to be included in the error count. I'm not sure how or why the $error.count is increased from this function.
Should I use a different variable to determine if the script "failed" or not? Is there some underlying reason why the $error.count would increase outside of the function that has the error while not increasing after the error is caught? Any guidance on this issue would be appreciated.
Code for reference:
Wrapper function:
$errorCount = $error.count
Write-Warning ("$errorCount Before function")
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction SilentlyContinue
$errorCount = $error.count
Write-Warning ("$errorCount After function")
} catch {
Write-Error "Error caught by wrapper: $_"
}
Extraction/Insertion_Function:
ForEach ($db in $dbList) {
Write-Warning "$errorCount database
.
.
.
try {
$totalProperties = Get-ServerDBMetadata -DBConnectionString ($connStr) -DatabaseName $dbName -EA SilentlyContinue
} catch {
Write-Warning "Unable to extract metadata from $dbname in $server"
}
}
I then have the error count printing out inside the loop that extracts/inserts the metadata from each database to the insertion database, as well as in the loop for each server that contains the databases:
WARNING: 0 Before function
WARNING: 0 database
.
.
.
WARNING: 0 database
WARNING: Unable to extract metadata from *database* in *server*
WARNING: 0 database
.
.
.
WARNING: 0 database
**WARNING: 1 After function**
The error (permission issue) is caught inside the function but cascades to my wrapper script. I want this particular error to be ignored while NOT ignoring other, more serious errors (like being unable to connect to the server I'm inserting the metadata into) so placing -EA Ignore on the driver function inside the wrapper script is out of the question.
Replace SilentlyContinue with Ignore to ignore the error and not have it increase the count.
Extraction/Insertion_Function -serverList $serverList -insertionDB $insertionDB -ErrorAction Ignore
To catch it inside the function, use -ErrorAction Stop as in thepip3r's answer as try/catch statements only catch terminating errors.
Your primary problem with the try-catch not catching the error (even though you don't supply all of the code) is that your cmdlet explicitly calls -ErrorAction SilentlyContinue. Try/Catch blocks REQUIRE the use of terminating errors so in the case of your function/cmdlet, you need to change to -ErrorAction Stop for try/catch to appropriately handle an error from that function/cmdlet.
This needs to be updated for any other function/cmdlet in the code we can't see.
Edit described in comments below:
$n = New-Object PSObject -property #{
'Test1' = ''
'Test2' = ''
'Test3' = ''
}
try {
get-process someprocess -ErrorAction Stop
$n.Test1 = $true
} catch {
$n.Test1 = $false
}
try {
Get-WmiObject win32_computersystem -ErrorAction Stop
$n.Test2 = $true
} catch {
$n.Test2 = $false
}
try {
Get-Content somefile.ext -ErrorAction Stop
$n.Test3 = $true
} catch {
$n.Test3 = $false
}
if ($n.Test1 -and $n.Test2 -and $n.Test3) {
## All procedures completed successfully -- do something magical
} else {
## At least one test procedure failed.
}
Logging the remaining errors since the last log:
While ($Global:ErrorCount -lt $Error.Count) {
$Err = $Error[$Error.Count - ++$Global:ErrorCount]
$ErrLine = "Error at $($Err.InvocationInfo.ScriptLineNumber),$($Err.InvocationInfo.OffsetInLine): $Err"
Write-Host $ErrLine -ForegroundColor Red # Log this
}

Why aren't error thrown when I run this function as domain administrator?

This script is intended to recurse through a series of directories and when an error of type DirUnauthorizedAccessError,Microsoft or PowerShell.Commands.GetChildItemCommand is thrown it's supposed to call another function Take-Ownership which takes ownership of the directory and adds full permissions for the localAdmin and domain admin to the folder. (It's really a script used for easing the deletion of old user profiles):
function Test-Folder($FolderToTest, $localAdminName) {
# Remeber the old error preference...
$old_ErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$error.Clear()
# Go through the directories...and capture errors in $error
Get-ChildItem $FolderToTest -Recurse -ErrorAction SilentlyContinue -ErrorVariable errz | Select FullName
Write-Host $errz.count
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
foreach ($err in $errz) {
Write-Host "Error: $err"
if ($err.FullyQualifiedErrorId -eq "DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand") {
Write-Host Unable to access $err.TargetObject -Fore Red
Write-Host Attempting to take ownership of $err.TargetObject -Fore Yellow
Take-Ownership -Folder $err.TargetObject, -LocalAdminName $localAdminName
Test-Folder -FolderToTest $err.TargetObject -localAdminName $localAdminName
}
}
}
$ErrorActionPreference = $old_ErrorActionPreference
}
Unfortunately, it doesn't throw any errors when I run it as domain administrator. I've found a list of ErrorActionPreferences here, but the errors just seem to get ignored, and it outputs blah no errors What can I do to make sure I receive errors and that my Take-Ownership function is actually called?
Your code only enters the if block if $errz.Count is 0. With a count of 0 there are no elements in $errz, so there's nothing to do for the foreach loop.
Add an else branch to the conditional, move the foreach loop there, and the code should do what you want.
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
} else {
foreach ($err in $errz) {
Write-Host "Error: $err"
...
}
}

Executing command doesn't result in script exception

We have a powershell script that deploys a database script. However, if the database script fails, the output doesn't throw exceptions to the powershell script.
Below is an example of the .ps1 file:
function Publish-DatabaseProject
{
sqlcmd -S . -b -v DatabaseName=Integration -q "alter table xx add test Varchar(10)"
}
function Add-Timestamp {
process {
if ($_.GetType() -eq [string]) {
"[$(Get-Date -Format o)] $_"
} else {
$_
}
}
}
function Write-LogFile {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)] [string] $Path,
[Parameter(Mandatory=$true, ValueFromPipeline=$true)] [object[]] $InputObject
)
begin {
$root = Split-Path -Path $Path -Parent
if ($root -and !(Test-Path -Path $root)) { New-Item -Path $root -Type Directory
| Out-Null }
}
process {
$InputObject |
Add-Timestamp |
Tee-Object -File $Path -Append
}
}
Publish-DatabaseProject -ErrorVariable DeployError 2>&1 |
Write-LogFile -Path "C:\output.log"
if ($DeployError -and $DeployError.Count -ne 0)
{
Write-Output "Failed"
} else
{
Write-Output "Succeeded"
}
The query in question is executing against a non-existent table. The text output shows:
[2015-12-11T14:42:45.1973944+00:00] Msg 4902, Level 16, State 1, Server ABDN-DEV-PC1, Line 1
[2015-12-11T14:42:45.2053944+00:00] Cannot find the object "xx" because it does not exist or you do not have permission
s.
Succeeded
I am expecting the last line to read: Failed.
If you run the sqlcmd line on its own, and follow it up with $LastExitCode, it does spit out a non-zero exit code.
> sqlcmd -S . -b -v DatabaseName=Integration -q "alter table xx add test Varchar(10)"
Msg 4902, Level 16, State 1, Server ABDN-DEV-PC1, Line 1
Cannot find the object "xx" because it does not exist or you do not have permissions.
> $LastExitCode
1
For various reasons, we cannot use Invoke-SqlCmd, and need to stick with SQLCMD.exe.
How can we make exceptions from within SQLCMD bubble out correctly to the calling script?
Your -ErrorVariable DeployError statement would only get triggered if the Publish-DatabaseProject cmdlet itself fails to execute. As that function is mostly a wrapper around sqlcmd.exe, there isn't any intelligence to bubble up this error. We can wrap it though by using the $LastExitCode automatic variable.
function Publish-DatabaseProject
{
sqlcmd -S . -b -v DatabaseName=Integration -q "alter table xx add test Varchar(10)"
if ($LastExitCode -ne 0)
{
Write-error $LastExitCode
throw $LastExitCode
}
}
Now, PowerShell will catch this error from the .exe, and you can use -ErrorVariable.
Update
So, since you want to keep running and not abandon ship when enountering an error, we need to wrap your Publish-DataBaseProject function with a try{}catch{} block, to catch the error we're generating, without stopping execution.
try {Publish-DatabaseProject -ErrorAction STOP -ErrorVariable DeployError 2>&1 |
Write-LogFile -Path "C:\output.log" }
catch{
Write-Output "Current job $($_), failed"
Write-Output "Exception $($Error.ExceptionID)"
}
Now we're properly generating an exception from a CMD, bubbling it up through our Function, and handling it as if it were a normal function, all using Native PowerShell. I believe this is what you were seeking to do. If not, post a gist of the whole script and I'll help you in a more targeted fashion.

Catch error and restart the if statement

I have a powershell script that adds a computer to a domain. Sometimes, when I run the script I get the following error and when I run it for the second time it works.
How can I make the script to check if I get this error, and if so then to retry adding it to the domain?
I have read that it is hard to try and catch errors like that. Is that correct? Is there a better/different way to catch the error?
Thank you!
Code:
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-Computer -DomainName my-domain.local | out-null
} else {...}
Error:
This command cannot be executed on target computer('computer-name') due to following error: The specified domain either does not exist or could not be contacted.
You can use the built in $Error variable. Clear it before executing code, then test if the count is gt 0 for post error code.
$Error.Clear()
Add-Computer -DomainName my-domain.local | out-null
if($Error.count -gt 0){
Start-Sleep -seconds 5
Add-Computer -DomainName my-domain.local | out-null}
}
You could setup a function to call itself on the Catch. Something like:
function Add-ComputerToAD{
Param([String]$Domain="my-domain.local")
Try{
Add-Computer -DomainName $Domain | out-null
}
Catch{
Add-ComputerToAD
}
}
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
Add-ComputerToAD
} else {...}
I haven't tried it to be honest, but I don't see why it wouldn't work. It is not specific to that error, so it'll infinitely loop on repeating errors (i.e. another computer with the same name exists in AD already, or you specify an invalid domain name).
Otherwise you could use a While loop. Something like
if ($localIpAddress -eq $newIP)
{ # Add the computer to the domain
write-host "Adding computer to my-domain.local.. "
While($Error[0].Exception -match "The specified domain either does not exist or could not be contacted"){
Add-Computer -DomainName my-domain.local | out-null
}
}