I have this script
ForEach ($u in $usersFromFile)
{
try{
$nomailbox = if (-not (get-mailbox $u.alias)){
$notfound = $notfound + $nomailbox
Write-host "No mailbox for "$u.alias -F blue|out-file "d:\scripts\nomailbox.txt"}
}
Catch{}
Nothing is written to the outfile
What am I missing?
TIA
Andy
If you are using PowerShell version lower than 5, Out-Host writes to host, just like PetSerAl said, you should use Write-Output, instead. With PS 5 you can use Out-Host to achieve what previously couldn't be achieved with Out-Host.
Just use:
ForEach ($u in $usersFromFile) { try{
$nomailbox = if (-not (get-mailbox $u.alias)){
$notfound = $notfound + $nomailbox
Write-Output "No mailbox for "$u.alias -F blue|out-file "d:\scripts\nomailbox.txt"}
}
Catch{}
This should work. But i think, youre not gonna color this line without a reason.. You can use this with a function too:
function writehosttofile {
Param(
[switch]$para
)
Write-Host "No mailbox for "$u.alias -F blue
if($para){
"No mailbox for " + $u.alias
}
}
writehosttofile -para | out-file "d:\scripts\nomailbox.txt"
Write-Host
DESCRIPTION
Write-Host sends the objects to the host. It does not return any objects.
You can also browse to your script and redirect all output in a file:
PS C:\temp> cd c:\bin
PS C:\bin> .\script.ps1 > C:\bin\script_output.txt
have a nice day.
Related
I would like to ask question about how I should proceed or how I should fix the code.
My problem is that I need my code to write into the Path three different paths for Logstash, Kibana and ElasticSearch, but I have no idea how to do it. It returns always the same error about missing ")" error
Here's the whole code ¨
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[string]$NewLocation.GetType($ElasticSearch)
[string]$ElasticSearch = "C:\Elastic_Test_Server\elasticsearch\bin"
[string]$Kibana = "C:\Elastic_Test_Server\kibana\bin"
[string]$Logstash = "C:\Elastic_Test_Server\logstash\bin"
)
Begin
{
#Je potřeba spustit jako Administrátor
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API errory
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Zkontroluje zda cesta již existuje
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Nová cesta
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Přidá to přítomné session
$env:path += ";$NewLocation"
# Uloží do registru
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
Thank you for your help.
I really think you could simplify this a lot, unless I have misunderstood. Apologies, I am not currently on a Windows machine so can't test this.
function Add-ESPath {
# Create an array of the paths we wish to add.
$ElasticSearch = #(
"C:\Elastic_Test_Server\elasticsearch\bin",
"C:\Elastic_Test_Server\kibana\bin",
"C:\Elastic_Test_Server\logstash\bin"
)
# Collect the current PATH string and split it out in to an array
$CurrentPath = [System.Environment]::GetEnvironmentVariable("PATH")
$PathArray = $CurrentPath -split ";"
# Loop though the paths we wish to add.
foreach ($Item in $ElasticSearch) {
if ($PathArray -notcontains $Item) {
$PathArray += $Item
}
else {
Write-Output -Message "$Item is already a member of the path." # Use Write-Warning if you wish. I see it more as a notification here.
}
}
# Set the path.
$PathString = $PathArray -join ";"
Try {
[System.Environment]::SetEnvironmentVariable("PATH", $PathString)
exit 0
}
Catch {
Write-Warning -Message "There was an issue setting PATH on this machine. The path was:" # Use $env:COMPUTERNAME here perhaps instead of 'this machine'.
Write-Warning -Message $PathString
Write-Warning -Message $_.Exception.Message
exit 1
}
}
Add-ESPath
Perhaps you want to add some kind of log file rather than writing messages/warnings to the console. You can use Add-Content for this.
I long time ago i wrote some functions to add a path to system path + their is an check if the path is already inside the system path. And i also did an elevation check so when i use this function and i forgot to elevate my powershell that i get a warning. Its a different approach, I hope it will help you.
I only use the begin {} proccess{} statements for when i want to write a function that excepts pipeline inputs. So its if you want to write a function that will work as the following:
$paths = #("C:\Elastic_Test_Server\elasticsearch\bin", "C:\Elastic_Test_Server\kibana\bin")
$paths | my-append-these-to-system-path-function
Elevation check:
function G-AmIelevated($warningMessage){
if([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")){
return $true
}else{
write-host "not elevated $warningMessage" -ForegroundColor Red
return $false
}
}
append something to system path with check if its already inside system path:
function G-appendSystemEnvironmentPath($str){
if(test-path $str){
if(!((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path) -like "*$str*")){
write-host "`t $str exists...`n adding $str to environmentPath" -ForegroundColor Yellow
if(G-AmIelevated){
write-host `t old: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' `
-Name Path `
-Value "$((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path);$str"
write-host `t new: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
write-host `t restart the computer for the changes to take effect -ForegroundColor Red
write-host `t `$Env:Path is the merge of System Path and User Path This function set the system path
write-host `t $str appended to environmet variables. -ForegroundColor Green
}else{
write-host `t rerun ise in elevated mode -ForegroundColor Red
}
}else{
write-host "`t $str is in system environmenth path"
}
}else{
write-host `t $str does not exist
}
}
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\elasticsearch\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\kibana\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\logstash\bin"
I am trying to write to the console :
Write-Host "Backing up Switch1"
Then run the backup and check if it was successful
Write-Host "Backing up $Building $SwitchName ..."
$initFldrCount = Get-ChildItem C:\Admin\$SwitchName | Measure-Object | ForEach-Object{$_.Count}
C:\kitty_portable-0.70.0.9.exe -ssh -l Admin -pw Password -cmd $Command $ip
$count++
Start-Sleep 5
$fnlFldrCount = Get-ChildItem C:\Admin\$SwitchName | Measure-Object | ForEach-Object{$_.Count}
#Make sure a backup was made
if (($initFldrCount + 1) -ne $fnlFldrCount){
$Status = "Failure"
}
else {
$Status = "Success"
}
Write-Host "===$Status==="
it then outputs
Backing up Switch-1 ...
===Failure===
But I want it to add failure to the end of the Backing up string after the
script
Backing up Switch-1 ... ===Failure===
How would I go about doing this
Simply add the -NoNewLine switch to the line
Write-Host "Backing up $Building $SwitchName ..."
as such:
Write-Host "Backing up $Building $SwitchName ..." -NoNewLine
Below function I want to pass multiple value in array. When I'm passing more than one value I am getting an error.
function CheckProcess([String[]]$sEnterComputerNameHere, [String[]]$sEnterProccessNameHere) {
#Write-Host " $sEnterComputerNameHere hello"
#($sEnterComputerNameHere) | ForEach-Object {
# Calling Aarray
#($sEnterProccessNameHere) | ForEach-Object {
if (Get-Process -ComputerName $sEnterComputerNameHere | where {$_.ProcessName -eq $sEnterProccessNameHere}) {
Write-Output "$_ is running"
} else {
Write-Output "$_ is not running"
}
}
}
}
$script:sEnterProccessNameHere = #("VPNUI") # Pass the process agreement here
$script:sEnterComputerNameHere = #("hostname") # Pass the process agreement here
CheckProcess $sEnterComputerNameHere $sEnterProccessNameHere
Give it a try with this one:
Function CheckProcess([String[]]$sEnterComputerNameHere,[String[]]$sEnterProccessNameHere)
{ #Write-host " $sEnterComputerNameHere"
#($sEnterComputerNameHere) | Foreach-Object {
$computer = $_
Write-Host $computer
#($sEnterProccessNameHere) | Foreach-Object {
$process = $_
Write-Host $process
try{
$x = get-process -computername $computer #Save all processes in a variable
If ($x.ProcessName -contains $process) #use contains instead of equals
{
Write-Output "$process is running"
}
else
{
Write-Output "$process is not running"
}
}
catch
{
Write-Host "Computer $computer not found" -ForegroundColor Yellow
}
}
}
}
$script:sEnterProccessNameHere = #("VPNUI","Notepad++","SMSS")
$script:sEnterComputerNameHere = #("remotecomputer1","remotecomputer2")
CheckProcess -sEnterComputerNameHere $sEnterComputerNameHere -sEnterProccessNameHere $sEnterProccessNameHere
In general, it would be great if you write the error you get in your question. That helps others to help you.
If I work with arrays and | Foreach, I always write the $_in a new variable. That helps if I have another | Foreach (like you had) to know for sure, with which object I'm working with..
EDIT: I changed the script, so it uses "-contains" instead of "-eq" and I added a try/catch block, so if the other computer is not found, it gives you a message.. It works on my network
EDIT2: Do you have access to the other computers? If you run get-process -computername "name of remote computer" do you get the processes?
Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?
For example:
$global:ERRORS = #();
$global:PROGERR = #();
function test() {
# Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
$OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
if ( $OUTPUT | select-string -Pattern "foo" ) {
# do stuff
} else {
$global:ERRORS += "test(): oh noes! 'foo' missing!";
}
}
test;
if ( #($global:ERRORS).length -gt 0 ) {
Write-Host "Script specific error occurred";
foreach ( $err in $global:ERRORS ) {
$host.ui.WriteErrorLine("err: $err");
}
} else {
Write-Host "Script ran fine!";
}
if ( #($global:PROGERR).length -gt 0 ) {
# do stuff
} else {
Write-Host "External program ran fine!";
}
A dull example however I am wondering if that is possible?
One option is to combine the output of stdout and stderr into a single stream, then filter.
Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
The easiest way to do this is to use a file for the stderr output, e.g.:
$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
I would also use $LastExitCode to check for errors from native console EXE files.
You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:
$command = "myexecutable.exe my command line params"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
It is just another possibility to supplement what was already given.
Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:
PowerShell -File ".\FileName.ps1"
An alternative that seems to work under most circumstances is this:
$stdOutAndError = Invoke-Expression "$command 2>&1"
Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.
In case you want to get any from a PowerShell script and to pass a function name followed by any arguments you can use dot sourcing to call the function name and its parameters.
Then using part of James answer to get the $output or the $errors.
The .ps1 file is called W:\Path With Spaces\Get-Something.ps1 with a function inside named Get-It and a parameter FilePath.
Both the paths are wrapped in quotes to prevent spaces in the paths breaking the command.
$command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
# This will get its output.
$output
# This will output the errors.
$errors
Copied from my answer on how to capture both output and verbose information in different variables.
Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.
In this way, it will not only take longer time, but also increase the probability of error.
In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).
# First, declare a method that outputs both streams at the same time.
function thisFunc {
[cmdletbinding()]
param()
Write-Output 'Output'
Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
Then we verify the contents of these two variables
$VerboseStream.getType().FullName
$String.getType().FullName
The following information should appear on the console:
PS> System.Management.Automation.VerboseRecord
System.String
'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.
Separately, preserving formatting
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta2=""
foreach ($el in $r.Meta2){
$Meta2+=$el
}
$Meta2=($Meta2 -split "[`r`n]") -join "`n"
$Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
[Console]::Write("stderr:`n");
[Console]::Write($Meta2);
[Console]::Write("`n");
$Meta3=""
foreach ($el in $r.Meta3){
$Meta3+=$el
}
$Meta3=($Meta3 -split "[`r`n]") -join "`n"
$Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
[Console]::Write("stdout:`n");
[Console]::Write($Meta3);
[Console]::Write("`n");
I have this test script to change the Administrator password on a list of servers.
I have set the script to log errors if the server can't be ping'd or account can't be found etc. However in addtion to this i'd like to capture any other errors that take place and also add those to the log file. I know you can use the "Try and Catch" for error handling but havn't had any luck so far.
Would someone be kind enough to show how to do it?
Here is the script
$date = Get-Date
$user = "Administrator"
$newpwd = "MyPassword"
$servers = gc C:\servers.txt
foreach ($server in $servers)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $null
$Reply = $ping.send($server)
if($Reply.status -like 'Success')
{
$Admin=[adsi]("WinNT://" + $server + "/$user, user")
if($?)
{
$Admin.SetPassword($newpwd)
if($?)
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Succsess the $user password was changed. , $date"}
else
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: FAILED to change the password. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: The $user user account was not found on the server. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: Ping FAILED could not connect. , $date"
}
If you want to write exceptions to the log right after they were thrown, you could use a trap. Add something like this to you script:
trap [Exception] {
#Add message to log
Add-Content -Path test.csv -Value "$server, $($_.Exception.Message), $(Get-Date)"
#Continue script
continue;
}
That will log all exceptions (not all errors).
If you want all errors, you can access them using $Error. It's an arraylist containing every error during your sessions(script). The first item $Error[0] is the latest error. This however, is not something that fits directly into an csv file without formatting it.