I have created a script in the Azure PowerShell.
If I use the "echo" command, it displays output to the console.
However, if I use Write-Output and Write-Error, I don't see the output.
I have uploaded the script "change-to-static.ps1" to a storage account. Then I open the "Cloud Shell" with a button at the top bar. Then I type "./change-ip-to-static.ps1" in the PowerShell console.
Therefore, the script does not produce any output unless I replace "Write-Output" and "Write-Error" with "echo" or "print".
Please help me. What should I do to see the output?
The script is below.
There is a similar question at How to output something in PowerShell. I have read it, but there are no concrete examples on how to achieve my goal, i.e. how to modify my script to see the output. And in my case, it does not output even if I redirect to a text file. However, commands like "echo" and "print" in my case work but they are not covered in the above example. See the script below.
$IPs = Get-AzPublicIpAddress;
$Static = "Static";
foreach ($PublicIP in $IPs) {
$Method = $PublicIP.PublicIpAllocationMethod;
$Name = $PublicIP.Name;
if ($Method -eq $Static) {
$message = "The method of " + $Name + " is already " + $Static;
Write-Progress -Activity $message;
}
else {
Write-Progress -Activity "Changing the method of "+$Name+" from "+$Method+" to "+$Static+"...";
$PublicIP.PublicIpAllocationMethod = $Static;
Set-AzPublicIpAddress -PublicIpAddress $PublicIP;
Write-Progress -Activity "Querying the method of "+$Name+"...";
$ModifiedAddress = Get-AzPublicIpAddress -Name $Name -ResourceGroupName $PublicIP.ResourceGroupName -Location $PublicIP.Location
$NewMethod = $ModifiedAddress.PublicIpAllocationMethod;
if ($NewMethod -eq $Static) {
Write-Output "The method for "+$Name+" has successfully changed to "+$Static;
}
else {
Write-Error -Message "Cannot change the method for "+$Name+" to "+$Static+", it is still "+$NewMethod+"!!!";
}
}
}
P.S. I have updated the script (use this URL) according to the suggestions, but there is still no output. Only "echo" or "print" gives the output.
P.P.S. The Write-Progress does not even show a temporary message in the status line during Set-AzPublicIpAddress which takes a couple of seconds to complete, or if I add the Start-Sleep cmdlet. It does only set during Get-AzPublicIpAddress.
After reading your last edit to my answer, I believe you made a bit of confusion in using Write-* commandlets, and in your script logic, so I provided a more detailed answer with context.
echo in the Powershell Azure Cloud Shell is an alias of Write-Output, as executing echo without parameters clearly shows (docs here ).
PS /home/mikelangelo> echo
cmdlet Write-Output at command pipeline position 1
Supply values for the following parameters:
InputObject:
Moreover: the unix echo can also be run in the Powershell Azure Cloud Shell.
PS /home/mikelangelo> which echo
/usr/bin/echo
PS /home/mikelangelo> /usr/bin/echo ciao mondo
ciao mondo
print, on the other hand, is not a Powershell alias, so the unix counterpart is the one which always get executed when using the print keyword (presently a symlink to run-mailcap - but it's not clear to me how it comes into play into your use case.)
PS /home/mikelangelo> which print
/usr/bin/print
So, basically, echo and Write-Output will both work, because they call the same commandlet, unless you execute /usr/bin/echo directly, mixing up technologies and effectively impairing portability.
Back to the question:
Write-Output works as expected. The logic is faulty: You use = as a comparison operator, but you need to use -eq instead.
Write-Progress needs to be used differently, replace it with Write-Host or Write-Output. Refer to the docs for an explanation.
Note that Write-Output sends an object down the pipeline, which can eventually be represented as a console output.
Write-Progress and Write-Host, on the other hand, do not generate output - the latter sends an object to the host for displaying, though, so Write-Host is the recommended way to display something in the console. Refer to this question for more details on Write-Host, Write-Output and the Powershell pipeline.
Like other commenters before me I can also confirm that the code from your gist works just fine in Azure Cloud Shell.
I noticed that there is only an output if you have at least one dynamic public ip that the script can change to static. The reason is, that only in this case you use Write-Output to return a string to the console.
If there is no dynamic public ip left, your script only writes a progress message, but you never get to see it, as the script execution ends too quickly after you write the message and progress messages don't linger.
Put the command Start-Sleep -Seconds 2 under the line with Write-Progress and you will see what I mean:
$IPs = Get-AzPublicIpAddress;
$Static = "Static";
foreach ($PublicIP in $IPs) {
$Method = $PublicIP.PublicIpAllocationMethod;
$Name = $PublicIP.Name;
if ($Method -eq $Static) {
$message = "The method of $Name is already $Static";
Write-Progress -Activity $message;
Start-Sleep -Seconds 2 # This will keep the script running 2 seconds longer and the message visible.
}
else {
Write-Progress -Activity "Changing the method of $Name from $Method to $Static ...";
$PublicIP.PublicIpAllocationMethod = $Static;
Set-AzPublicIpAddress -PublicIpAddress $PublicIP;
Write-Progress -Activity "Querying the method of $Name ...";
$ModifiedAddress = Get-AzPublicIpAddress -Name $Name -ResourceGroupName $PublicIP.ResourceGroupName
$NewMethod = $ModifiedAddress.PublicIpAllocationMethod;
if ($NewMethod -eq $Static) {
Write-Output "The method for $Name has successfully changed to $Static";
}
else {
Write-Error -Message "Cannot change the method for $Name to $Static, it is still $NewMethod!!!";
}
}
}
Write-Progress is probably not the cmdlet that you want to use to write out the progress of your script (despite it's name). As I do not see how you would need to further process the output of your script, you might as well replace it with Write-Host.
Related
If I run this powershell code locally:
$url = "refs/pull/5625/merge"
$SourceBranchFromBuild = $url.split('/')[-1]
$featureReleaseUrl = "http://$sourceBranchFromBuild.azurewebsites.net"
Write-Output $featureReleaseUrl
The output is:
http://merge.azurewebsites.net
When I run this code on a Azure Powershell:
$url = "refs/pull/5625/merge"
$SourceBranchFromBuild = $url.split('/')[-1]
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]"$SourceBranchFromBuild
And then create the URL in another Azure Powershell script:
$featureReleaseUrl = "http://$env:prSourceBranchName.azurewebsites.net"
Write-Output $featureReleaseUrl
The ouput is
http:// merge.azurewebsites.net
What's causing this leading space character in the $env:prSourceBranchName?
Azure isn't adding anything - your write-host is!
Your code is doing this:
PS> $x = "xxx"
PS> write-host "aaa"$x
aaa xxx
but presumably you want
PS> $x = "xxx"
PS> write-host "aaa$x"
aaaxxx
Note where the second quote is the write-host in both examples. In the first it's before the $x variable name. In the second it's after.
In your question it's calling this (with the quote before the variable name):
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]"$SourceBranchFromBuild
which will write a logging command to the log file, and Azure DevOps will process that and update the environment variable.
You're probably expecting it to write this to the log file:
##vso[task.setvariable variable=prSourceBranchName;]merge
but it's actually writing this:
##vso[task.setvariable variable=prSourceBranchName;] merge
Try switching your code to this (i.e. second quote after the variable name):
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]$SourceBranchFromBuild"
and it should omit the space in front of the branch name in your url.
#PeterBoomsma Try putting $SourceBranchFromBuild inside the double quotes like this:
Write-Host "##vso[task.setvariable variable=prSourceBranchName;]$SourceBranchFromBuild"
I have a list of devices that I would like to ping against to see if they are active or not. If they are active (even if not), I would like to log to a file. So, the active devices go to one file, and the "unpingable" devices are written to another file. The script I have below works okay for a smaller sampling of computers, but when I add the complete list, the "test-connection" section of the script takes hours to complete (about 8,000 devices). Is there a way to improve the performance by running the command in parallel? In my search, i came across this wrapper function by David Wyatt => TestConnectionAsync but I'm unsure how to make it work with separating results in two files. Any help would be appreciated.
Code:
ForEach ($PC in $Computer_List) {
If (Test-Connection -ComputerName $PC -Quiet -Count 1) {
Add-Content -value $PC -Path "$UPpath"
} Else {
Add-Content -Value $PC -Path "$DOWNpath"
}
}
The output from Test-ConnectionAsync makes this quite easy:
When you specify the -Quiet switch of Test-ConnectionAsync, it returns a collection of PSCustomObjects with the properties "ComputerName" and "Success". Success is the boolean value you'd have received from Test-Connection -Quiet ; the PSCustomObject just allows you to associate that result with the target address.
So all you've got to do is:
Ping all computers and capture the output
Look at the Success attribute to see if you should put it in one file or another
# 1. Gather ping results
$TestResults = $ComputerList | Test-ConnectionAsync -Quiet
# 2. Loop through results and look at Success
foreach($Result in $TestResults){
$FilePath = if($Result.Success){
$UPpath
} else {
$DOWNpath
}
Add-Content -Path $FilePath -Value $Result.ComputerName
}
I am looking for a bit of help, hope nobody will bash me for being an ignorant.
Not that long ago I became something of an AD admin, organisation is big so the tasks vary. I can easily complete what I require via Powershell or snap-ins in most cases.
However I have a task on my hands that exceed my "creativity". I have a list of over 10 000 users in .csv which I need to look up in on-premises AD if they exist. My two problems are:
-I am very new to scripting and getting increasingly frustrated that I can't comprehend it and make my scripts work as I need them to
-Deadline for this task and other responsibilities give me little time to read more on scripting basics and learn. As such I am in most cases forced to look for script snippets on the web and modify them a bit to meet my needs. This worked up until now as the script I have on my hands is a bit too complex for me.
Biggest problem I was facing so far is creating a forest-wide search. My organization have a single root domain and 4 child domains. When running a simple foreach loop a like the one below:
ForEach ($User in (Import-Csv c:\users\public\users.csv))
{ If (Get-ADUser $User.mail -server GLOBALCATALOGADDRESS:xxxx)
{ Write-Host "User found: $($User.mail)"
}
Else
{ Write-Host "User not found: $($User.mail)"
}
}
It searches only domain to which my computer is connected.
So I managed to find and modify a forest-wide search script and came up with following:
#Get Domain List
$objForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = #($objForest.Domains | Select-Object Name)
$Domains = $DomainList | foreach {$_.Name}
$User = Import-CSV c:\users\public\users.csv
#Act on each domain
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
$ADsPath = [ADSI]"LDAP://$Domain"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher($ADsPath)
#The filter
Foreach($mail in($User))
{
$objSearcher.Filter = "(&(objectCategory=user)(mail=$User.mail))"
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$objArray = $objResult.GetDirectoryEntry()
write-host $objArray.mail
}
}
}
The script seems to be good in its original form (found here: http://powershell.nicoh.me/powershell-1/active-directory/forest-wide-object-searches) and searches well with wildcard and single parameter as filter.
However I have no idea what am I missing to make it search for every email address I have in .csv and to make it return information whether or not user with such mail was found.
Script itself runs but given the time it takes and blank output it feels like it searches for only one user. I am 100% sure that at least one user from the list exists in on-prem AD.
Any suggestions are very welcome.
Thanks for your attention.
[EDIT]
Final script:
#Get Domain List and load user e-mails from file
$objForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = #($objForest.Domains | Select-Object Name)
$Domains = $DomainList | foreach {$_.Name}
$Users = Import-CSV c:\users\public\users.csv
#Act on each domain
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
Foreach($mail in ($Users.mail))
{
Get-ADUser -filter {mail -eq $mail} -Server $domain -properties mail | select mail
}
}
Do yourself a favour and download AD Powershell module: http://blogs.msdn.com/b/rkramesh/archive/2012/01/17/how-to-add-active-directory-module-in-powershell-in-windows-7.aspx
You will then be able to simplify your code and run things like this, making your task much clearer:
...
foreach($Domain in ($Domains))
{
Write-Host "Checking $Domain" -fore red
Foreach($mail in ($User.mail))
{
Get-ADUser -filter {mail -eq $mail} -Server $domain -Properties mail |
select-object -ExpandProperty mail
}
}
...
More on AD PS cmdlets: http://technet.microsoft.com/en-us/library/ee617195.aspx
Use -LDAPfilter & point the -Server to GC.
Get-ADUser -Server DC01.Contoso.com:3268
-Ldapfilter "(ObjectClass=user)(mailnickname=David)"
The above command will search the GC DC01.contoso.com for all the users that their Alias/mailnickname is David.
Is is enough to contact the Domain itself instead of a DC of the domain. Thus this shoud also work
get-aduser -Filter {mailnickname -eq "David") -Server contoso.com:3268
Below is a small PowerShell script that runs some PowerShell code asynchronously to show a dialog box (for the purpose of demonstrating my issue).
If I close the parent PowerShell process, the child process will also close and the dialog box disappears. Is there any way to launch a PowerShell scriptblock, complete with functions and arguments, asynchronously and without a dependence on the parent PowerShell process?
$testjob = [PowerShell]::Create().AddScript({ $a = new-object -comobject wscript.shell
$b = $a.popup('This is a test',5,'Test Message Box',1) })
$result = $testJob.BeginInvoke()
Update #2
I am trying to execute a script block, rather than an external script. The script block should use functions and variables from the parent script. The problem is, I can't pass those functions or variables in to the new process unless they are contained directly within the script block. Any idea if this is doable?
Function Show-Prompt {
Param ($title,$message)
$a = new-object -comobject wscript.shell
$b = $a.popup($message,5,$title,1)
}
$scriptContent = {Show-Prompt -Message 'This is a test' -Title 'Test Message Box'}
$scriptBlock = [scriptblock]::Create($scriptContent)
Start-process powershell -argumentlist "-noexit -command &{$scriptBlock}"
You can use an intermediate PowerShell process. There has to be a direct parent to process the return value from the async script. For instance, put your script in a file called popup.ps1 and try execute like so:
Start-Process PowerShell -Arg c:\popup.ps1
You might want to bump the timeout up a bit from say 5 to 10 seconds. You can close the original PowerShell and the popup will stay. When you close the popup (or it times out) the secondary PowerShell window will disappear.
You can do this with WMI. If you use Win32_Process to create the process, it will persist after you close PowerShell.
For instance:
invoke-wmimethod -path win32_process -name create -argumentlist calc
function GeneratePortableFunction {
param ([string] $name, [scriptblock] $sb)
$block = [ScriptBlock]::Create("return `${function:$name};");
$script = $block.Invoke();
$block = [ScriptBlock]::Create($script);
return ("function {0} {{ {1} }}" -f $name, $block.ToString());
}
function RemoteScript {
param ([string] $header, [string[]] $functions, [string] $footer)
$sb = New-Object System.Text.StringBuilder;
[void]$sb.Append("$header`n");
$functions | % {
[void]$sb.Append($_);
[void]$sb.Append("`n");
}
[void]$sb.Append($footer);
return [ScriptBlock]::Create($sb.ToString());
}
$fnc = #();
$fnc += GeneratePortableFunction -name "NameOfYourFunction1";
$fnc += GeneratePortableFunction -name "NameOfYourFunction2(CallsNameOfYourFunction1)";
$script = RemoteScript -header "param([int] `$param1)" -functions #($fnc) -footer "NameOfYourFunction2 -YourParameter `$param1;";
$p1 = 0;
$job = Start-Job -ScriptBlock $script -ArgumentList #($p1);
while($job.State -eq "Running")
{
write-host "Running...";
Start-Sleep 5;
}
$result = $job | Receive-Job;
Remove-Job -Id $job.Id;
I have a server with lots of media drives ~43TB. An areca 1882ix-16 is set to spin the drives down after 30 minutes of inactivity since most days an individual drive is not even used. This works nicely to prevent unnecessary power and heat. In this case the drives still show up in windows explorer but when you click to access them it takes about 10 seconds for the folder list to show up since it has to wait for the drive to spin up.
For administrative work I have a need to spin up all the drives to be able to search among them. Clicking on each drive in windows explorer and then waiting for it to spin up before clicking the next drive is very tedious. Obviously multiple explorer windows makes it faster but it is still tedious. I thought a powershell script may ease the pain.
So I started with the following:
$mediaDrives = #('E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'K:', 'L:',
'M:','N:', 'O:', 'P:', 'Q:', 'R:', 'S:')
get-childitem $mediaDrives | foreach-object -process { $_.Name }
This is just requesting that each drive in the array have its root folder name listed. That works to wake the drive but it is again a linear function. The script pauses for each drive before printing. Looking for a solution as to how to wake each drive simultaneously. Is there a way to multi-thread or something else?
Here's a script that will do what you want, but it must be run under powershell using the MTA threading mode (which is the default for powershell.exe 2.0, but powershell.exe 3.0 must be launched with the -MTA switch.)
#require -version 2.0
# if running in ISE or in STA console, abort
if (($host.runspace.apartmentstate -eq "STA") -or $psise) {
write-warning "This script must be run under powershell -MTA"
exit
}
$mediaDrives = #('E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'K:', 'L:',
'M:','N:', 'O:', 'P:', 'Q:', 'R:', 'S:')
# create a pool of 8 runspaces
$pool = [runspacefactory]::CreateRunspacePool(1, 8)
$pool.Open()
$jobs = #()
$ps = #()
$wait = #()
$count = $mediaDrives.Length
for ($i = 0; $i -lt $count; $i++) {
# create a "powershell pipeline runner"
$ps += [powershell]::create()
# assign our pool of 8 runspaces to use
$ps[$i].runspacepool = $pool
# add wake drive command
[void]$ps[$i].AddScript(
"dir $($mediaDrives[$i]) > `$null")
# start script asynchronously
$jobs += $ps[$i].BeginInvoke();
# store wait handles for WaitForAll call
$wait += $jobs[$i].AsyncWaitHandle
}
# wait 5 minutes for all jobs to finish (configurable)
$success = [System.Threading.WaitHandle]::WaitAll($wait,
(new-timespan -Minutes 5))
write-host "All completed? $success"
# end async call
for ($i = 0; $i -lt $count; $i++) {
write-host "Completing async pipeline job $i"
try {
# complete async job
$ps[$i].EndInvoke($jobs[$i])
} catch {
# oops-ee!
write-warning "error: $_"
}
# dump info about completed pipelines
$info = $ps[$i].InvocationStateInfo
write-host "State: $($info.state) ; Reason: $($info.reason)"
}
So, for example, save as warmup.ps1 and run like: powershell -mta c:\scripts\warmup.ps1
To read more about runspace pools and the general technique above, take a look at my blog entry about runspacepools:
http://nivot.org/blog/post/2009/01/22/CTP3TheRunspaceFactoryAndPowerShellAccelerators
I chose 8 pretty much arbitrarily for the parallelism factor - experiment yourself with lower or higher numbers.
Spin up a separate powershell instance for each drive or use workflows in PowerShell 3.0.
Anyhow, you can pass drives directly to the Path parameter and skip Foreach-Object all togeteher:
Get-ChildItem $mediaDrives
Have you considered approaching this with the Start-Job cmdlet:
$mediaDrives = #('E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'K:')
$mediaDrives | ForEach-Object {
Start-Job -ArgumentList $_ -ScriptBlock {param($drive)
Get-ChildItem $drive
}
}
The only clever part is that you need to use the -ArgumentList parameter on the Start-Job cmdlet to pass the correct value through for each iteration. This will create a background task that runs in parallel with the execution of the script. If you are curious
If you don't want to wait, well, don't wait: start those wake-up calls in the background.
In bash one would write
foreach drive ($mediadrives) {tickle_and_wake $drive &}
(note the ampersand, which means: start the command in the background, don't wait for it to complete)
In PowerShell that would translate to something like
foreach ($drive in $mediadrives) {
Start-Job {param($d) tickle_and_wake $d} -Arg $drive
}
If you want confirmation that all background jobs have completed, use wait in bash or Wait-Job in Powershell