I would like to change the language in this script to Norwegian and believe I have done it successfully, however, this change has suppressed error messages that are output. The same issue is present with Using-Culture en-us.
Any ideas on why this happens and how to fix it if possible?
My method of testing what the output error is is by creating a new VPN connection with the same name as an existing one.
Without the Using-Culture function, errors are shown. With it, the errors are suppressed and the script only shows the custom message (Which is shown when an error is present regardless).
Example below shows errors as intended, but does not change the language to Norwegian:
try
{
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -assecurestring "Please enter your Pre-shared Key"
#Default Cisco Meraki parameters
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("VPN-profile for $Name has been created.
You may now use this connection.
Username and password are required on the first-time sign on.
Support: contact | company",0,"Completed") | Out-Null
}
catch
{
Write-Error $_.Exception.ToString()
Read-Host -Prompt "The above error occurred, please try again. If the issue persists, please contact support.
Support: contact | company
Please press Enter to exit"
}
See full script below. This does not show errors, and I can not confirm if the language is Norwegian in the errors.
Function Using-Culture([Globalization.CultureInfo]$culture, [ScriptBlock]$script) {
$OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
trap {
[System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
}
[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
$ExecutionContext.InvokeCommand.InvokeScript($script)
[System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
}
Using-Culture nb-NO {
try {
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -AsSecureString "Please enter your Pre-shared Key"
# Default Cisco Meraki parameters
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
# Gives popup with information on next steps
$wshell = New-Object -ComObject WScript.Shell
$wshell.Popup("VPN-profile for $Name has been created.`nYou may now use this connection.`nUsername and password is required on first time sign on.`nSupport: contact | company", 0, "Completed") | Out-Null
} catch {
# Reports error and suppresses "Completed"
Write-Error $_.Exception.ToString()
Read-Host -Prompt "The above error occurred, please try again. If the issue persists, please contact support.`support: contact | company`nPlease press Enter to exit"
}
}
I apologize for grammatical errors as English is not my first language.
The top example-script is not optimized, as it is a testing script.
Related
I'm looking to create a simple powershell script that will import the user's first name from file, prompt to create a new password and loop on error when the password doesn't meet the password requirement based on the "ErrorVariable" if possible. If not, please advise.
# import user firstname from file
$firstname = $(Get-Content "C:\tmp\local\firstname.txt")
# prompt user for new password
$password = Read-Host "Hello, $firstname!! Please change your local admin account password. (Requirements: At least 8-characters, 1-Cap Letter, 1-Number) " -AsSecureString -Erroraction silentlycontinue -ErrorVariable PasswordError
# create new password
$password = $password
Get-LocalUser -Name "$firstname" | Set-LocalUser -Password $password -Erroraction silentlycontinue -ErrorVariable PasswordError
If ($PasswordError)
{
"Unable to update the password. The new password does not meet the length or complexity."
}
If (-Not $PasswordError)
{
"Password updated successfully!!"
See script above.........
Think you could simply use try/catch - e.g.:
try {
Set-LocalUser -Name $firstname -Password $password -Erroraction:stop
write-host "Password updated successfully!!"
}
Catch {
write-error $_
}
If the operation succeeded you will get "Password updated successfully!!", otherwise it returns the error.
I'm trying to write a script to remotely rename multiple computers. Here's what I have (I know the Verify function works so that can be skipped over. The issue is occurring with the GetComputers function)
function main{
$DomainCredential = Verify
$computers = GetComputers
#Rename -computers $computers -DomainCredential $DomainCredential
}
function Verify{
# Prompt for Credentials and verify them using the DirectoryServices.AccountManagement assembly.
Write-Host "Please provide your credentials so the script can continue."
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
# Extract the current user's domain and also pre-format the user name to be used in the credential prompt.
$UserDomain = $env:USERDOMAIN
$UserName = "$UserDomain\$env:USERNAME"
# Define the starting number (always #1) and the desired maximum number of attempts, and the initial credential prompt message to use.
$Attempt = 1
$MaxAttempts = 5
$CredentialPrompt = "Enter your Domain account password (attempt #$Attempt out of $MaxAttempts):"
# Set ValidAccount to false so it can be used to exit the loop when a valid account is found (and the value is changed to $True).
$ValidAccount = $False
# Loop through prompting for and validating credentials, until the credentials are confirmed, or the maximum number of attempts is reached.
Do {
# Blank any previous failure messages and then prompt for credentials with the custom message and the pre-populated domain\user name.
$FailureMessage = $Null
$Credentials = Get-Credential -UserName $UserName -Message $CredentialPrompt
# Verify the credentials prompt wasn't bypassed.
If ($Credentials) {
# If the user name was changed, then switch to using it for this and future credential prompt validations.
If ($Credentials.UserName -ne $UserName) {
$UserName = $Credentials.UserName
}
# Test the user name (even if it was changed in the credential prompt) and password.
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
Try {
$PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $ContextType,$UserDomain
} Catch {
If ($_.Exception.InnerException -like "*The server could not be contacted*") {
$FailureMessage = "Could not contact a server for the specified domain on attempt #$Attempt out of $MaxAttempts."
} Else {
$FailureMessage = "Unpredicted failure: `"$($_.Exception.Message)`" on attempt #$Attempt out of $MaxAttempts."
}
}
# If there wasn't a failure talking to the domain test the validation of the credentials, and if it fails record a failure message.
If (-not($FailureMessage)) {
$ValidAccount = $PrincipalContext.ValidateCredentials($UserName,$Credentials.GetNetworkCredential().Password)
If (-not($ValidAccount)) {
$FailureMessage = "Bad user name or password used on credential prompt attempt #$Attempt out of $MaxAttempts."
}
}
# Otherwise the credential prompt was (most likely accidentally) bypassed so record a failure message.
} Else {
$FailureMessage = "Credential prompt closed/skipped on attempt #$Attempt out of $MaxAttempts."
}
# If there was a failure message recorded above, display it, and update credential prompt message.
If ($FailureMessage) {
Write-Warning "$FailureMessage"
$Attempt++
If ($Attempt -lt $MaxAttempts) {
$CredentialPrompt = "Authentication error. Please try again (attempt #$Attempt out of $MaxAttempts):"
} ElseIf ($Attempt -eq $MaxAttempts) {
$CredentialPrompt = "Authentication error. THIS IS YOUR LAST CHANCE (attempt #$Attempt out of $MaxAttempts):"
}
}
} Until (($ValidAccount) -or ($Attempt -gt $MaxAttempts))
# If the credentials weren't successfully verified, then exit the script.
If (-not($ValidAccount)) {
Write-Host -ForegroundColor Red "You failed $MaxAttempts attempts at providing a valid user credentials. Exiting the script now... "
EXIT
} Else {
Write-Host "Credntials authenticated"
return $Credentials
}
}
function GetComputers{
$oldnames = New-Object System.Collections.ArrayList
Write-Output "Enter the PC numbers to be named. Do not include 'PC' only type the following numbers. Type 'end' when finished"
$userinput = Read-Host
while($userinput -ne "end"){
$userinput = "$('PC')$($userinput)"
[void]$oldnames.Add($userinput)
$userinput = Read-Host
}
return $oldnames
}
workflow Rename($computers, $DomainCredential){
foreach -parallel ($computer in $computers){
$newname = "$($computer)$('MK')"
Rename-Computer -PSComputerName $computer -NewName $newname -DomainCredential $DomainCredential
}
}
main
The Verify function works perfectly, but then it just hangs and nothing else happens. I added a debug line between the call of the Verify and the call of the GetComputer functions, and that also printed out. Im new to powershell and am out of ideas
Are you certain that it is hanging, or is Read-Host just showing a blank input location? Calling that function without any arguments will just give you a blinking cursor in command line. Try adding some form of prompt to read-host, like below:
PS C:\Users\mbolton> $var=read-host
"string"
PS C:\Users\mbolton> $var
"string"
PS C:\Users\mbolton> $var=read-host "type something in"
type something in: "different string"
PS C:\Users\mbolton> $var
"different string"
PS C:\Users\mbolton>
I have created a PowerShell script that will add a VPN connection for Cisco Meraki.
The script itself functions as intended, but if a error occures, the "Completed" popup appears, with the error message shown in the PS windows.
Is it possible to supress the error and show a custom error popup based on the error that appears, while stopping the "Completed" popup from appearing?
I am aware of the $ErrorActionPreference= 'silentlycontinue', but unsure of how to implement this with a custom error.
Script to add VPN connections for Cisco Meraki.
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -assecurestring "Please enter your Pre-shared Key"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("VPN-profile for $Name has been created.
You may now use this connection.
Username and password is required on first time sign on.
Support: _witheld_ | _witheld_",0,"Completed")
Since your script continues to run after the error occurs, you are dealing with a non-terminating error, so you can use the -ErrorVariable common parameter to capture a given cmdlet invocation's error(s).
Using a simplified example, which you can apply analogously to your Add-VpnConnection call:
# Call Get-Item with a nonexistent path, which causes a *non-terminating* error.
# * Capture the error with -ErrorVariable in variable $err.
# * Suppress the error console output with -ErrorAction SilentlyContinue
Get-Item /NoSuch/Path -ErrorVariable err -ErrorAction SilentlyContinue
$null = (New-Object -ComObject Wscript.Shell).Popup(
$(if ($err) { "Error: $err" } else { 'Success.' })
)
If you were dealing with a terminating error, you'd have to use try / catch:
# Call Get-Item with an unsupported parameter, which causes a
# *(statement-)terminating* error.
try {
Get-Item -NoSuchParam
} catch {
# Save the error, which is a [System.Management.Automation.ErrorRecord]
# instance. To save just a the *message* (a string), use
# err = "$_"
$err = $_
}
$null = (New-Object -ComObject Wscript.Shell).Popup(
$(if ($err) { "Error: $err" } else { 'Success.' })
)
Note:
Neither -ErrorAction nor -ErrorVariable work with terminating errors.
Conversely, try / catch cannot be used to handle non-terminating errors, which is presumably why Ranadip Dutta's answer didn't work for you.
For a comprehensive discussion of PowerShell error handling, see this GitHub issue.
You have to have the error handling for the script. I have given it as a whole in the below script but you can configure it based on your need:
try
{
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -assecurestring "Please enter your Pre-shared Key"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("VPN-profile for $Name has been created.You may now use this connection.Username and password is required on first time sign on.Support: _witheld_ | _witheld_",0,"Completed")
}
catch
{
"Your custom message"
$_.Exception.Message
}
For further refence, read TRY/CATCH/FINALLY in Powershell
Hope it helps.
there!!
Here is my goal, I'm trying accomplish with Powershell script.
I have administrative user that need to be able extract csv file from password protected zip file and send this csv file by email.
Given directory has many zip files named by username (e.g. dana.zip) and protected with the same password (123456). The administrative user (who know the password for zip files) needs to run powershell script which asking for input desired user name and then do it's staff - extract file to the same directory and send it by email.
So far I find and adopt for above needs following powershell scripts.
Unzip Password protected file:
$7ZipPath = '"C:\Program Files\7-Zip\7z.exe"'
$User = Read-Host -Prompt 'Please Input Desired User Name'
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
write-host " Desired file will be extracted to W:\ADMINISTRATION folder " -foregroundcolor Cyan
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
$zipFile = '"W:\ADMINISTRATION\$User.zip"'
$zipFilePassword = "123456"
$command = "& $7ZipPath e -oW:\ADMINISTRATION -y -tzip -p$zipFilePassword $zipFile"
iex $command
This script doing it's job, but I'm trying to avoid usage of password as plain text in the script. Since this script will be run under same administrative user account I've tried to use encrypted password file in the script.
First, I've run following command to create encrypted password file:
"123456" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File "W:\Admin\ZipPassword.txt"
Afterwards I've adopt my script to use encrypted password file:
$7ZipPath = '"C:\Program Files\7-Zip\7z.exe"'
$User = Read-Host -Prompt 'Please Input Desired User Name'
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
write-host " Desired file will be extracted to W:\\ADMINISTRATION folder " -foregroundcolor Cyan
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
$zipFile = '"W:\ADMINISTRATION\$User.zip"'
$cred = Get-Content "W:\Admin\ZipPassword.txt" | ConvertTo-SecureString
$zipFilePassword = new-object -typename System.Management.Automation.PSCredential -argumentlist ($cred)
$command = "& $7ZipPath e -oW:\ADMINISTRATION -y -tzip -p$zipFilePassword $zipFile"
iex $command
When running this script I'm getting following error:
If it possible to make this script use encrypted password file it will me very beneficial...
The second script - sending extracted file by email.
First, I've created encrypted password file (in this script it 's working perfectly):
"myPassword" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File "W:\Admin\EmailPassword.txt"
And here is the script itself:
$User = "me.me#gmail.com"
$File = "W:\Admin\EmailPassword.txt"
$cred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $File | ConvertTo-SecureString)
$EmailTo = "me.me#gmail.com"
$EmailFrom = "me.me#gmail.com"
$Subject = "Some text here"
$Body = "Some text here"
$SMTPServer = "smtp.gmail.com"
$filenameAndPath = "W:\ADMINISTRATION\dana.csv"
$SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom,$EmailTo,$Subject,$Body)
$Attachment = New-Object System.Net.Mail.Attachment($filenameAndPath)
$SMTPMessage.Attachments.Add($attachment)
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($cred.UserName, $cred.Password);
$SMTPClient.Send($SMTPMessage)
write-host "Mail Sent Successfully !!" -foregroundcolor Green
This script working as expected... The only problem is that administrative user need edit it each time and put proper filename (dana.csv, david.csc... etc). Of course I can use user input method in this script as well, but I want to combine both scripts into the single one... So far I tried this one:
$7ZipPath = '"C:\Program Files\7-Zip\7z.exe"'
$User = Read-Host -Prompt 'Please Input Desired User Name'
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
write-host " Desired file will be extracted to W:\\ADMINISTRATION folder " -foregroundcolor Cyan
write-host ""
write-host " --------------------------------------------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
$zipFile = '"W:\ADMINISTRATION\$User.zip"'
$zipFilePassword = "123456"
$command = "& $7ZipPath e -oW:\ADMINISTRATION -y -tzip -p$zipFilePassword $zipFile"
iex $command
$User = "me.me#gmail.com"
$File = "W:\Admin\EmailPassword.txt"
$cred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $File | ConvertTo-SecureString)
$EmailTo = "me.me#gmail.com"
$EmailFrom = "me.me#gmail.com"
$Subject = "Some text here"
$Body = "Some text here"
$SMTPServer = "smtp.gmail.com"
$filenameAndPath = "W:\ADMINISTRATION\$User.csv"
$SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom,$EmailTo,$Subject,$Body)
$Attachment = New-Object System.Net.Mail.Attachment($filenameAndPath)
$SMTPMessage.Attachments.Add($attachment)
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($cred.UserName, $cred.Password);
$SMTPClient.Send($SMTPMessage)
write-host "Mail Sent Successfully !!" -foregroundcolor Green
But it's failed to attach file to email. I think I have problem here (wrong syntax):
$filenameAndPath = "W:\ADMINISTRATION\$User.csv"
So, if someone can help me to fix following issues, it will be much appreciated:
In the first portion of script, use encrypted password file instead of plain text
In the second portion, adopt user input from the first part of the script ($User) to be used as file name (for instance, if user input was "dana" , $User.csv will be equal to dana.csv)
Remove *.csv file after mail was sent.
Thank you in advance,
Igor.
I've fixed all the issues after "googling" them, So I can share my experience...
In the first portion of script, use encrypted password file instead of plain text
Like TheIncorrigible1 mentioned in his answer on my question:
You cannot completely avoid having a reversible password in your
script while using an external command like 7z unless it also supports
encrypted passwords being passed to it. SecurePassword is a
Windows-specific construct.
It looks like 7zip not supporting Windows-specific encrypted password, so I need convert it back to plain text before using it.
I've found how to do it in those sites:
https://blogs.msdn.microsoft.com/timid/2009/09/10/powershell-one-liner-decrypt-securestring/#comment-6495
and
http://www.travisgan.com/2015/06/powershell-password-encryption.html
So this peace of code helped me out fixing encrypted password issue:
$Password = Get-Content 'W:\Administration\EncryptedPasswords\ZipPassword.txt'| ConvertTo-SecureString
$Marshal = [System.Runtime.InteropServices.Marshal]
$Bstr = $Marshal::SecureStringToBSTR($Password)
$ZipPassword = $Marshal::PtrToStringAuto($Bstr)
$Marshal::ZeroFreeBSTR($Bstr)
In the second portion, adopt user input from the first part of the script ($User) to be used as file name (for instance, if user input
was "dana" , $User.csv will be equal to dana.csv)
This was my mistake,- I've used the same variable $User in the first and second portions of script, so $User variable from second portion of script override existing one... Therefore file wasn't attached to email. After changing variable in the first portion of script, issue goes away:
$Username = Read-Host -Prompt 'Please Input Desired User Name'....
$zipFile = "W:\ADMINISTRATION\$UserName.zip"...
$filenameAndPath = "W:\ADMINISTRATION\$UserName.csv"
Remove *.csv file after mail was sent.
I've accomplished this goal using separate PowerShell script:
Get-ChildItem W:\Administration\*.csv | Where { ! $_.PSIsContainer } | remove-item
write-host " ---------------------------------------------- " -foregroundcolor DarkCyan
write-host ""
write-host " All Operations Completed Successfully !!" -foregroundcolor Green
write-host ""
write-host " ---------------------------------------------- " -foregroundcolor DarkCyan
So in my script I want to not only have the user enter and store credentials in a variable but be able to verify that the password matches the admin password on the target system. So far the only way I have found to do this is by putting the actual password unecrypted in the script and comparing it to the one the user enters. That is a huge security flaw and to remedy it I was wondering if I could get the admin password using a gwmi query (SID?) as an object and compare that to the secure string the user enters.
Here is my flawed code I am using right now.
Do
{
$password = $null
$password = read-host "Enter the Administrator Password" -assecurestring
$AdminPass = ConvertTo-SecureString "adminpassword" -AsPlainText -Force
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($AdminPass))
if ($pwd1_text -cne $pwd2_text) {Write-Host -ForegroundColor Red "Incorrect Password"; $password = $null}
$count ++
$tries = 3 - $count
if ($password -eq $null) {Write-Host -ForegroundColor Yellow "$tries Attempts Remaining"}
if ($count -eq 3) {Write-Host -ForegroundColor Red "$count Unsuccessful Password Attempts. Exiting..."; exit}
}While ($password -eq $null)
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "$ComputerName\Administrator",$password
Here's a function I wrote that tests a PSCredential object, against a Domain or a local Machine:
function Test-Credential {
<#
.SYNOPSIS
Takes a PSCredential object and validates it against the domain (or local machine, or ADAM instance).
.PARAMETER cred
A PScredential object with the username/password you wish to test. Typically this is generated using the Get-Credential cmdlet. Accepts pipeline input.
.PARAMETER context
An optional parameter specifying what type of credential this is. Possible values are 'Domain' for Active Directory accounts, and 'Machine' for local machine accounts. The default is 'Domain.'
.OUTPUTS
A boolean, indicating whether the credentials were successfully validated.
.NOTES
Created by Jeffrey B Smith, 6/30/2010
#>
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[System.Management.Automation.PSCredential]$credential,
[parameter()][validateset('Domain','Machine')]
[string]$context = 'Domain'
)
begin {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::$context)
}
process {
$DS.ValidateCredentials($credential.GetNetworkCredential().UserName, $credential.GetNetworkCredential().password)
}
}
If you want to test against local accounts on a remote machine, you'll need to load this function on the remote machine and test the credential against the 'local' machine via remoting (Invoke-Command), but it should be possible.