I'm trying to gracefully exit the program if the username/password is incorrect or (ORA-01017) but the script hangs when getting an error. My proposed error is in the try section of the code then the entire script hangs. I thought the catch is suppose to throw the exception. Ideally I will create a for loop that will connect to each database and if there is an error just capture the error and go on to the next. But right now just testing the try/catching the error option. Also, I'm encrypting the password. Any ideas or suggestions
enter code here
function Start-Something
{
$User = Read-Host -Prompt 'Input the Oracle database user name'
$psswd = Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString
$cryptpasswd = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($psswd))
Write-Host "###############################################"
Write-Host Connecting to Oracle Database: testdb -ForegroundColor Green
sqlplus -s $User/$cryptpasswd#$line '#C:\Users\my\Documents\Scripts\oracledbinfo.sql'
Write-Host "###############################################"
Write-Host " ###########################################"
}
try
{
Start-Something
}
catch
{
Write-Output "Something threw an exception"
Write-Output $_
}
try
{
Start-Something -ErrorAction Stop
}
catch
{
Write-Output "Something threw an exception or used Write-Error"
Write-Output $_
}
Try/Catch does not hang. Looks like this line of code is waiting for input:
sqlplus -s $User/$cryptpasswd#$line '#C:\Users\my\Documents\Scripts\oracledbinfo.sql'
To verify run the codeblock of the function directly in the shell.
Currently you ask for the password and convert the string entered to a secureString Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString then you probably try to convert the value back to string, because sqlplus won't accept a secureString. But you missed a part =(System.Runtime.InteropServices.Marshal]::PtrToStringAuto())...
Anyways, here are some tips for you:
Take a look at the oracle .NET provider its the better choice on PowerShell.
If you want the user to input credentials use the get-credential cmdlet. By doing so you get a credential object back which you can use to authenticate or by calling the method getNetworkCredential() to get the password in cleartext.
Implement also a try/catch block within the function and return errors always with write-error and learn about output streams. try/Catch will only consider terminating errors, but sqlplus won't give you that -> oracle .NET provider.
Preface:
Toni's answer makes a good point that the apparent hang may be due to sqlplus waiting for an interactive response. If that is the case, that problem must be solved first.
A simpler way to convert a SecureString instance to plain text - which notably defeats the (limited) security benefits that SecureString provides (on Windows only) - is the following:
[pscredential]::new('unused', $psswd).GetNetworkCredential().Password
As an inconsequential aside, with respect to your approach: it should be [Runtime.InteropServices.Marshal]::PtrToStringBSTR(...), not [Runtime.InteropServices.Marshal]::PtrToStringAuto(...)
As of PowerShell 7.2.x, calls to external programs (such as sqlplus) never cause a (statement-)terminating error[1] that you can trap with try / catch
A potential future feature - which in preview versions of v7.3 is available as an experimental feature that may or may not become official - may allow better integration with PowerShell's error handling, by setting the $PSNativeCommandUseErrorActionPreference preference variable to $true.
Unfortunately, as of v7.3.0-preview.8, the error that is reported in response to a nonzero exit code (see below) is unexpectedly a non-terminating error, which means it can not be trapped with try / catch by the caller - see GitHub issue #18368.
To determine if a call to an external program call failed, examine its process exit code, as reflected in the automatic $LASTEXITCODE variable afterwards.
By convention, exit code 0 signals success, any nonzero value failure.
Therefore:
# Note: Write-Host calls omitted for brevity.
function Start-Something {
$User = Read-Host -Prompt 'Input the Oracle database user name'
$psswd = Read-Host -Prompt 'Input Oracle database Password:' -AsSecureString
$psswdClearText = [pscredential]::new('unused', $psswd).GetNetworkCredential().Password
# Invoke sqlplus.
# !! AS NOTED, YOU MAY HAVE TO MODIFY THIS COMMAND TO PREVENT AN INTERACTIVE PROMPT.
# Stderr output from this call, if any, goes directly to the display.
sqlplus -s $User/$psswdClearText#$line '#C:\Users\my\Documents\Scripts\oracledbinfo.sql'
# If sqlplus reported a nonzero exit code,
# throw a script-terminating error that the caller must handle with try / catch.
if ($LASTEXITCODE -ne 0) {
throw "sqlplus signaled failure: exit code is: $LASTEXITCODE"
}
}
try {
Start-Something
}
catch {
# Convert the script-terminating error into a non-terminating one.
$_ | Write-Error
}
[1] There are only two exceptions: (a) If the external program cannot even be invoked, e.g., because its name/path is misspelled or it isn't installed. Or (b), due to a bug, present up to PowerShell 7.1, where the combination of redirecting stderr output (2> or *>) with $ErrorActionPreference = 'Stop' unexpectedly caused a script-terminating error if there's actual stderr output - see this answer.
Related
Seems that New-CMTaskSequenceDeployment / Set-CMTaskSequenceDeployment cmdlet option -DeploymentOption does not work as expected.
I'm trying to automate a Task Sequence Deployment via Powershell. I use New-CMTaskSequenceDeployment cmdlet to create the deployment. The content of the TS should be downloaded before the start of the TS.
Works ok, but the -DeploymentOption DownloadAllContentLocallyBeforeStartingTaskSequence seems not to have any effect, when I check the deployment after the script ran, the option "pre-download content for this task sequence" isn't checked.
Same issue when I try Set-CMTaskSequenceDeployment.
Any hint from the community what I'm doing wrong?
...
# Create deployment for all waves now
foreach ($StrCollectionName in $ArrCollectionName)
{
$SchedulePhase2 = New-CMSchedule -Nonrecurring -Start $DateScheduleStartPhase2
Try {
$Deployment = New-CMTaskSequenceDeployment -CollectionName $StrCollectionName -TaskSequencePackageId $StrTaskSequenceID -DeployPurpose Required -AvailableDateTime $DateAvailablePhase1 -DeploymentOption DownloadAllContentLocallyBeforeStartingTaskSequence -SoftwareInstallation $False -SystemRestart $False -Schedule $SchedulePhase2 -RerunBehavior RerunIfFailedPreviousAttempt -AllowUsersRunIndependently $True -SendWakeUpPacket $True
Write-Host "Success - Deployment $Deployment created!"
}
Catch {
Write-Host "Error - Exception caught in creating deployment : $error[0]"
Exit
}
}
...
Looks like unfortunately (and unexpected) the pre-download behavior is different for package/program deployment and task sequence deployment.
In case of a package/program deployment the content download will start after start time in case the deployment has a mandatory time defined.
A TS deployment behaves different. It will start download when mandatory time (schedule) has been reached. Start time will be ignored.
This difference is independently from how the deployment has been started (Console or powershell cmdlet) therefore it is not an issue of the cmdlet.
First of all, you can check the picture below to make sure not to confuse these two options.
Difference between Preload content checkbox and Download all content locally before starting TS
Once done Here is my proposition :
Just by clicking, try to retrieve the property of you TSDeployment before and after you clicked the checkbox. You will see that one property changed. AdvertFlags
PS MUZ:\> (Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5).AdvertFlags
[Convert]::ToString((Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5).AdvertFlags,2)
Output :
34275328
10000010110000000000000000
From there, you can read from the MS doc : https://learn.microsoft.com/en-us/configmgr/develop/reference/core/servers/configure/sms_advertisement-server-wmi-class
From this, I learn that I need to change the 12th bit like this :
$advertflag = Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5
$advertflag.AdvertFlags = $advertflag.AdvertFlags -bor "0x00001000"
$advertflag.put()
I hope it will help someone someday :)
I am trying to troubleshoot an old TCL accounting script called GOTS - Grant Of The System. What it does is creates a time stamped logfile entry for each user login and another for the logout. The problem is it is not creating the second log file entry on logout. I think I tracked down the area where it is going wrong and I have attached it here. FYI the log file exists and it does not exit with the error "GOTS was called incorrectly!!". It should be executing the if then for [string match "$argv" "end_session"]
This software runs properly on RHEL Linux 6.9 but fails as described on Centos 7. I am thinking that there is a system variable or difference in the $argv argument vector for the different systems that creates this behavior.
Am I correct in suspecting $argv and if not does anyone see the true problem?
How do I print or display the $argv values on logout?
# Find out if we're beginning or ending a session
if { [string match "$argv" "end_session"] } {
if { ![file writable $Log] } {
onErrorNotify "4 LOG"
}
set ifd [open $Log a]
puts $ifd "[clock format [clock seconds]]\t$Instrument\t$LogName\t$GroupName"
close $ifd
unset ifd
exit 0
} elseif { [string match "$argv" "begin_session"] == 0 } {
puts stderr "GOTS was called incorrectly!!"
exit -1
}
end_session is populated by the /etc/gdm/PostSession/Default file
#!/bin/sh
### Begin GOTS PostSession
# Do not run GOTS if root is logging out
if test "${USER}" == "root" ; then
exit 0
fi
/usr/local/lib/GOTS/gots end_session > /var/tmp/gots_postsession.log 2> /var/tmp/gots_postsession.log
exit 0
### End GOTS PostSession
This is the postsession log file:
Application initialization failed: couldn't connect to display ":1"
Error in startup script: invalid command name "option"
while executing
"option add *Font "-adobe-new century schoolbook-medium-r-*-*-*-140-*-*-*-*-*-*""
(file "/usr/local/lib/GOTS/gots" line 26)
After a lot of troubleshooting we have determined that for whatever reason Centos is not allowing part of the /etc/gdm/PostSession/default file to execute:
fi
/usr/local/lib/GOTS/gots end_session
But it does update the PostSession.log file as it should .. . Does anyone have any idea what could be interfering with only part of the PostSession/default?
Does anyone have any idea what could be interfereing with PostSession/default?
Could it be that you are hitting Bug 851769?
That said, am I correct in stating that, as your investigation shows, this is not a Tcl-related issue or question anymore?
So it turns out that our script has certain elements that depend upon the Xserver running on logout to display some of the GUI error messages. This from:
Gnome Configuration
"When a user terminates their session, GDM will run the PostSession script. Note that the Xserver will have been stopped by the time this script is run, so it should not be accessed.
Note that the PostSession script will be run even when the display fails to respond due to an I/O error or similar. Thus, there is no guarantee that X applications will work during script execution."
We are having to rewrite those error message callouts so they simply write the errors to a file instead of depending on the display. The errors are for things that should be there in the beginning anyway.
I have a beginner's knowledge in scripting and programming and have a set of PowerShell commands that I am having issues with figuring out how to turn it into a script.
I can successfully run the following remote commands from my Windows 7 machine by running the Exchange 2007 PowerShell console as my domain administrator account and then running the following commands to pass the commands to the Exchange 2013 hybrid server for use with Office 365:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://hybridexch2013.contoso.com/PowerShell/ -Authentication Kerberos
Import-PSSession $Session
Enable-RemoteMailbox jame.doe#contoso.com -RemoteRoutingAddress jane.doe#contosoinc.mail.onmicrosoft.com
The more I look into this I don't know that I am making progress. See below for how my logic is working in my head on this. I understand this is incorrect and incomplete. I have commented out a function I had earlier but wasn't sure if I was doing it correct or if it was needed at all.
param($enableMailbox)
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://hybridexch2013.contoso.com/PowerShell/ -Authentication Kerberos
Import-PSSession $Session -AllowClobber
$fname = Read-Host "What is the user's first name?"
$lname = Read-Host "What is the user's last name?"
#function Func($enableMailbox)
#{
$enableMailbox = "Enable-RemoteMailbox $fname.$lname#contoso.com -RemoteRoutingAddress $fname.$lname#contosoinc.mail.onmicrosoft.com"
#}
#Func $enableMailbox
Write-Host $enableMailbox
I also find that if I manually run:
Enable-RemoteMailbox $fname.$lname#contoso.com -RemoteRoutingAddress $fname.$lname#contosoinc.mail.onmicrosoft.com
I get nothing. So I am not even understanding how you pass variables to the string to run the command correctly. Even if I run:
$fname = "Jane"
$lname = "Doe"
$enableMailbox = "Enable-RemoteMailbox $fname.$lname#contoso.com -RemoteRoutingAddress $fname.$lname#contosoinc.mail.onmicrosoft.com"
Write-Host $enableMailbox
I get no results.
I was trying to understand the param function using help from these pages: Powershell script with params *and* functions
Passing a variable to a powershell script via command line
https://devcentral.f5.com/blogs/us/powershell-abcs-p-is-for-parameters
http://www.experts-exchange.com/Programming/Languages/Scripting/Powershell/Q_27900846.html
But I am finding the param function difficult to understand and not sure I am even going in the right direction here. So far the only thing that seems to work is connecting to the PowerShell remotely.
Please help if I am am to be helped with this one and lack of my abilities here.
Param Function in short...
Function Write-Something
{
Param($InputText)
Write-Host $InputText
}
Write-Something Hello
Result is: Hello
Use Param Section to add Parameters to your Function like in the example above,
So for your Script:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://hybridexch2013.contoso.com/PowerShell/ -Authentication Kerberos
Import-PSSession $Session -AllowClobber
Function Enable-MBX
{
Param($Fname,$Lname)
Enable-RemoteMailbox "$Fname $Lname" -RemoteRoutingAddress "$fname.$lname#contoso.mail.onmicrosoft.com"
}
Enable-MBX Jane Doe
I think you're just confused about Write-Host.
I'm not certain if you're trying to write the enable-remotemailbox to console or execute it. The code you have should work fine for writing to console (screen) but won't execute the command. To execute the command:
Enable-RemoteMailbox "$fname.$lname#contoso.com" -RemoteRoutingAddress "$fname.$lname#contosoinc.mail.onmicrosoft.com"
Anything inside of double-quotes will expand. Ex: "$fname" will expand to "Bob" if $fname is equal to "Bob". If you encapsulate the whole command in quotes in a variable though, the Write-Host will NOT execute the command. Write-Host is designed to write output to the screen so you'll just see the command in the console.
If you want to further expand, you can use the sub-string operator $(). Ex:
Write-Host "$($fname.length).$($lname.length)"
Comes back as "3.5" for "Bob" and "Smith".
To avoid expansion, use single quotes:
Write-Host '$($fname.length).$($lname.length)'
Writes "$($fname.length).$($lname.length)" to the console.
The following (similar to your code) without quotes: Write-Host $fname.$lname#contoso.com will try to pull the $lname#contoso.com Property of $fname which in this context doesn't make sense (doesn't exist). To elaborate, it would be equivalent to Write-Host $fname.($lname#contoso.com).
I have a powershell script that is searching the Windows Search index directly for emails and files. I have the following code:
$searchme="my thing to find"
$sql="SELECT System.FileName, System.ItemPathDisplay, System.DateCreated, System.DateModified, system.itemurl, system.itemtypetext FROM SYSTEMINDEX WHERE Contains(System.FileName, '"+$searchme+"') OR Contains('"+$searchme+"')"
$adapter = new-object system.data.oledb.oleDBDataadapter -argumentlist $sql, "Provider=Search.CollatorDSO;Extended Properties=’Application=Windows’;"
$ds = new-object system.data.dataset
$adapter.Fill($ds)
foreach ($record in $ds.Tables[0].Rows)
{
$exeparams = $record[4]
write-host $exeparams
write-host $exeparams.split(":")[0]
if ($exeparams.split(":")[0] -eq "mapi15")
{
$exeparams2="mapi://" + $exeparams.substring(8)
}
write-host $exeparams
write-host "start"
$exe="start"
$exe+" "+$exeparams | Out-File 'file.txt' -encoding Unicode
write-host "start-process"
Start-Process $exeparams
Start-Process $exeparams2
write-host "andpersand process"
&$exe $exeparams
&$exe $exeparams2
write-host "dotnet"
$proc = [Diagnostics.Process]::Start($exeparams)
$proc.WaitForExit()
$proc = [Diagnostics.Process]::Start($exeparams2)
$proc.WaitForExit()
}
There are several "shell" calls because I was trying to figure out if it was a process spawning issue. Files work with no issue. Emails however fail with either No such interface if i leave mapi15, or Unspecified error if i change mapi15 to mapi. I believe that Open mails in outlook from java using the protocol "mapi://" may be the solution, but if it is I am not sure how to apply this in powershell. Thank you for your help.
Ok, that took more work than I expected, and I blame Office 2013 for it. Here's the short answer:
$exeparams2 = $exeparams -replace "^mapi15", "mapi"
& start $exeparams2
That is the code that now opens an email for me. That code did not do that yesterday, all it did is tell me:
Either there is no default mail client or the current mail client cannot fulfill the messaging request. Please run Microsoft Outlook and set it as the default mail client.
Infuriating is what this was, because I did have Outlook, in fact it was running, and was the default mail application for everything email related. This annoyed me, and sent me on a quest to figure out WTF was wrong, and if I could fix it. The answers to that are "I'm not real sure WTF was wrong, except maybe a naming change on MS's part", and "yes, yes I can fix it".
I finally found the fix that worked for me (and I believe that this is probably Office 2013/Office 365 specific) was found at the bottom of this thread:
https://social.technet.microsoft.com/Forums/office/en-US/64c0bedf-2bcd-40aa-bb9c-ad5de20c84be/cannot-send-email-with-microsoft-outlook-2010-64-bit-from-adobe-outlook-not-recognised-as?forum=outlook
The process is simple. Change 2 Registry Entries, then re-set your default mail application. Registry entries:
HKLM:\Software\Clients\Mail(default)
HKLM:\Software\Clients\Mail\Microsoft Outlook(default)
Change the value of (Default) from "Microsoft Outlook" to simply "Outlook".
After that I set Outlook to be the default for everything it could be ( in Win8 that's Control Panel > All Control Panel Items > Default Programs > Set Default Programs then select Outlook, and choose the first option to make it default for all extensions that it is registered for). Once that was done I was able to run the modified code above to launch an email that I had search the index for.
I have a simple script running in Powershell 3.0 :
# Testing write-debug
[CmdletBinding()]
param()
write-debug "Debug message"
Write-Output "General output"
When I run it without parameters, I get the desired output:
PS C:\scripts\Test> .\debugtest.ps1
General output
When I run it with the -debug parameter, Powershell asks me to confirm after printing the Debug message:
PS C:\scripts\Test> .\debugtest.ps1 -Debug
DEBUG: Debug message
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"):
General output
Why am I being asked to confirm? Shouldn't write-debug simply write the debug output and continue with the script?
Update: $DebugPreference is set to SilentlyContinue:
PS C:\scripts\Test> $DebugPreference
SilentlyContinue
PS C:\scripts\Test> .\debugtest.ps1 -Debug
DEBUG: Debug message
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"):
General output
It sounds like your $DebugPreference variable is set to 'Inquire'.
From Get-Help about_preference_variables:
$DebugPreference
------------------
Determines how Windows PowerShell responds to debugging messages
generated by a script, cmdlet or provider, or by a Write-Debug
command at the command line.
Some cmdlets display debugging messages, which are typically very
technical messages designed for programmers and technical support
professionals. By default, debugging messages are not displayed,
but you can display debugging messages by changing the value of
$DebugPreference.
You can also use the Debug common parameter of a cmdlet to display
or hide the debugging messages for a specific command. For more
information, type: "get-help about_commonparameters".
Valid values:
Stop: Displays the debug message and stops
executing. Writes an error to the console.
Inquire: Displays the debug message and asks you
whether you want to continue. Note that
adding the Debug common parameter to a
command--when the command is configured
to generate a debugging message--changes
the value of the $DebugPreference variable
to Inquire.
Continue: Displays the debug message and continues
with execution.
SilentlyContinue: No effect. The debug message is not
(Default) displayed and execution continues without
interruption.
Edit: -Debug is also a cmdlet Common Parameter, and by adding CmdletBinding(), it is also a Common Parameter of your script.
From Get-Help about_common_parameters:
COMMON PARAMETER DESCRIPTIONS
-Debug[:{$true | $false}]
Alias: db
Displays programmer-level detail about the operation performed by the
command. This parameter works only when the command generates
a debugging message. For example, this parameter works when a command
contains the Write-Debug cmdlet.
**The Debug parameter overrides the value of the $DebugPreference
variable for the current command, setting the value of $DebugPreference
to Inquire.** Because the default value of the $DebugPreference variable
is SilentlyContinue, debugging messages are not displayed by default.
Valid values:
$true (-Debug:$true). Has the same effect as -Debug.
$false (-Debug:$false). Suppresses the display of debugging
messages when the value of the $DebugPreference is not
SilentlyContinue (the default).
You can override $DebugPreference in the script:
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if ($PSBoundParameters['Debug']) {
$DebugPreference = 'Continue'
}
Write-Output "General output"
Write-Debug "Debug message"
I think second parameter declarationof the following function can be added any function
function SetDebugPreference {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$true,
HelpMessage="Please enter for `$DebugPreference a value`n('SilentlyContinue','Continue' ,'Inquire' or 'Stop')",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0
)]
[ValidateSet("SilentlyContinue","Continue" ,"Inquire","Stop")]
[Alias("dbp","dbPref")]
[string]
$dbPreference,
[Parameter(
Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=1
)]
[ValidateSet("SilentlyContinue","Continue" ,"Inquire","Stop")]
[Alias("ldbp","ldbPref")]
[string]
$LocalDebugPreference="Continue"
)
Begin {
Write-Verbose ("Local DebugPreference: " + $DebugPreference)
$DebugPreference=$LocalDebugPreference
Write-Verbose ("Local DebugPreference set: " + $LocalDebugPreference)
Write-Debug "Local debug test"
}
Process {
Write-Verbose ("Global DebugPreference: " + $Global:DebugPreference)
$Global:DebugPreference=$dbPreference
Write-Verbose ("Global DebugPreference set: " + $Global:DebugPreference)
}
}