Azure DevOps Azure PowerShell task output variable - azure-devops

I'm creating a release pipeline using one Azure PowerShell Task and PowerShell task. In the the Azure Powershell Task, I have the following code
$groupInfos = #()
for ([int]$i = 0; $i -lt $azureADGroupsObj.Count)
{
$groupInfo = New-Object PSObject
$groupInfo | Add-Member -MemberType NoteProperty -Name "displayName" -Value $azureADGroupsObj[$i].DisplayName
$groupInfo | Add-Member -MemberType NoteProperty -Name "Id" -Value
$azureADGroupsObj[$i].Id
$groupInfos += $groupInfo
$i++
}
return $groupInfos
Write-Host "##vso[task.setvariable variable=azureADGroups;]$groupInfos"
I am trying to store $groupInfos into azureADGroups variable here.
but when I run a PowerShell task in the next step under same job, it says the term "azureADGroup" is not recognized.. seems like the variable wasn't set..does anyone know what am I missing here?

I found 3 problems in your script:
You do not need to set the reference name.
There is a return before the write variable command. So, the write variable command will not be executed.
The write variable command can only use single-line string. However, the $groupInfos is an object. It will not be implicitly converted to a string. You need to use "ConvertTo-Json -Compress" command to convert it to a string.
I tested at my pipeline:
$groupInfosString = $groupInfos | ConvertTo-Json -Compress
write-host $groupInfos
write-host $groupInfosString
Write-Host "##vso[task.setvariable variable=azureADGroups;]$groupInfos"
Write-Host "##vso[task.setvariable variable=azureADGroupsFromString;]$groupInfosString "
From the debug log, we can check that variable "azureADGroupsFromString" is successfully set.
Update:
You can use the following script in next PS task:
$objs = '$(azureADGroupsFromString)' | ConvertFrom-Json
foreach( $obj in $objs){
Write-Host ("displayName:{0} Id:{1}" -f $obj.displayName, $obj.Id)
}
Output:
Update:
If you want to pass it to next PS task via arguments, please enclose the variable in single quotes. In this way, it will be considered as a string.

Related

Powershell error writing writing to all users' registry

I'm attempting to add a wallpaper, along with certain parameters, to each user on a computer. It's been hit and miss with this working/not working on computers. The ones that fail I get the error "Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'."
The variables $WallpaperPath and $Style are coming from another source within Automation Manager (using N-Central).
# Get each user profile SID and Path to the profile
$UserProfiles = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" | Where {$_.PSChildName -match "S-1-5-21-(\d+-?){4}$" } | Select-Object #{Name="SID"; Expression={$_.PSChildName}}, #{Name="UserHive";Expression={"$($_.ProfileImagePath)\NTuser.dat"}}
# Add in the .DEFAULT User Profile
$DefaultProfile = "" | Select-Object SID, UserHive
$DefaultProfile.SID = ".DEFAULT"
$DefaultProfile.Userhive = "C:\Users\Public\NTuser.dat"
$UserProfiles += $DefaultProfile
# Loop through each profile on the machine</p>
Foreach ($UserProfile in $UserProfiles) {
    # Load User ntuser.dat if it's not already loaded
    If (($ProfileWasLoaded = Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
        Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE LOAD HKU\$($UserProfile.SID) $($UserProfile.UserHive)" -Wait -WindowStyle Hidden
    }
# Write to the registry
$key = "Registry::HKEY_USERS\$($UserProfile.SID)\Control Panel\Desktop"
Set-ItemProperty -Path $key -name Wallpaper -value "$WallpaperPath"
Set-ItemProperty -Path $key -name TileWallpaper -value "0"
Set-ItemProperty -Path $key -name WallpaperStyle -value "$Style" -Force
# Unload NTuser.dat
If ($ProfileWasLoaded -eq $false) {
    [gc]::Collect()
    Start-Sleep 1
    Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE UNLOAD HKU\$($UserProfile.SID)" -Wait -WindowStyle Hidden| Out-Null
}
}
I'm looking for this to load a temporary HKU hive for each user that's not currently logged in, and has an NTuser.dat file, and write the registry entries specified. It should then unload any hive for users it added.
Instead of $UserProfiles = ..., use either [array] $UserProfiles = ... or $UserProfiles = #(...) in order to ensure that $UserProfiles always contains an array, even if the command happens to return just one object.
That way, your += operation is guaranteed to work as intended, namely to (loosely speaking) append an element to the array.[1]
Note that PowerShell's pipeline has no concept of an array, just a stream of objects. When such a stream is collected, a single object is captured as itself; only two or more objects are captured in an array ([object[]]) - see this answer for more information.
A simple demonstration:
2, 1 | ForEach-Object {
$result = Get-ChildItem / | Select-Object Name -First $_
try {
$result += [pscustomobject] #{ Name = 'another name' }
"`$result has $($result.Count) elements."
} catch {
Write-Warning "+= operation failed: $_"
}
}
In the first iteration, 2 objects are returned, which are
stored in an array. += is then used to "append" another element.
In the second iteration, only 1 object is returned and stored as such.
Since [pscustomobject], which is the type of object returned by Select-Object, doesn't define a + operation (which would have
to be implemented via an op_Addition() method at the .NET level), the error you saw occurs.
Using an [array] type constraint or #(...), the array-subexpression operator operator, avoids this problem:
2, 1 | ForEach-Object {
# Note the use of #(...)
# Alternatively:
# [array] $result = Get-ChildItem \ | Select-Object Name -First $_
$result = #(Get-ChildItem / | Select-Object Name -First $_)
$result += [pscustomobject] #{ Name = 'another name' }
"`$result has $($result.Count) elements."
}
As noted, [array] $results = Get-ChildItem \ | Select-Object Name -First $_ works too, though there are subtle differences between the two approaches - see this answer.
As an aside:
To synchronously execute console applications or batch files and capture their output, call them directly (c:\path\to\some.exe ... or & $exePath ...), do not use Start-Process (or the System.Diagnostics.Process API it is based on) - see this answer. GitHub docs issue #6239 provides guidance on when use of Start-Process is and isn't appropriate.
That is, you can just make calls such as the following:
REG.EXE LOAD "HKU\$($UserProfile.SID)" "$($UserProfile.UserHive)"
Also, it's easier and more efficient to construct [pscustomobject] instances with their literal syntax (v3+; see the conceptual about_PSCustomObject help topic):
$UserProfiles += [pscustomobject] #{
SID = ".DEFAULT"
Userhive = "C:\Users\Public\NTuser.dat"
}
[1] Technically, a new array must be created behind the scenes, given that arrays are fixed-size data structures. While += is convenient, it is therefore inefficient, which matters in loops - see this answer.

Check Which Path exists and IF...Test-path PS

Actual File Configuration:
clustername=GLA-CLU2
fswmain=\DC01\SQL-FSW1
fswdr=\DC01\SQL-FSW2
My actual script:
### BEGIN OF Passing as Variables, the configuration values separated with '=' and Retrieved from the indicated TXT file:
Get-Content '.\FSW_pathConf.txt' | Foreach-Object {
$Vconfvalue = $_.Split('=')
New-Variable -Name $Vconfvalue[0] -Value $Vconfvalue[1] }
### END OF Passing values as Variables
### Testing Each Path defined into configuration file
$currentfsw = Get-clusterresource -cluster $clustername | where-object {$_.ResourceType -like "File Share Witness"} | get-clusterparameter -Name "sharepath" | Select Value
Write-Host "`r"
Write-Host "the current file share witness configuration On Cluster: '$($Clustername)' is: '$($currentfsw)' "
if($currentfsw -is "$fswmain") {Test-Path -Path "$fswmain" -IsValid}
else{Set-ClusterQuorum -Cluster $Clustername -NodeAndFileShareMajority "$fswdr"}
if($currentfsw -is "$fswdr") { Test-Path -Path "$fswdr" -IsValid}
else{Set-ClusterQuorum -Cluster $Clustername -NodeAndFileShareMajority "$fswmain" }
What I would like to do whcih im struggling with:
if $fswmain is valid then return "OK,current fsw is working" and stop this script here
If not $currentfsw is no valid then run the command below to swith to $fswdr
Then, only if $currentfsw is not equal to $fswmain
if $fswdr is valid then return "OK,current fsw is working" and stop this script here
If not run the command below to switch to $fswmain
Any could bring some help here ? I am quite new with powershell and sysadmin
Thank you.

PowerShell $PROFILE, Variables and NoteProperty / Add-Member

$profile is causing me some headaches. $Profile.GetType() resolves to String which is fine, but it has NoteProperty values:
$profile | Get-Member -Type NoteProperty
AllUsersAllHosts, AllUsersCurrentHost, CurrentUserAllHosts, CurrentUserCurrentHost
When I type $profile, the CurrentUserCurrentHost NoteProperty is returned. This is fine, but I need to change this value - it's complicated, but my corporate VPN uses a network profile and when I am online it tries to reference that location for my $profile meaning that every console startup takes 9 seconds (horribly slow). I can get that down to 1 sec if the profile is loaded locally, but that means changing these values. To do this, I put the following into the AllUsersAllHosts profile.ps1
$profile = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
That's fine, but by doing this, I find that all of the NoteProperty values are deleted! So I tried:
$profile.CurrentUserCurrentHost = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
But this fails, as the root value of $Profile continues to point at the network profile and Console startup is 9 seconds again!
I then also noticed the following weirdness:
$x = [string]$profile
$x -eq $profile
Ny main questions are:
• Why does $x return True even though $x has none of the NoteProperty values in $profile (as the objects are definitely not the same!)?
• How do I control what the root value of $profile is without destroying the NoteProperty values, and
• How can I update .CurrentUserAllHosts and .CurrentUserCurrentHost in such a way that the root value will also update accordingly? i.e. Even if I do the below, the root value of $profile remains unchanged (still points at the very slow network profile location):
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersAllHosts" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersCurrentHost" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserAllHosts" -NotePropertyValue "C:\Users\$($env:Username)\Documents\WindowsPowerShell\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserCurrentHost" -NotePropertyValue "C:\Users\$($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
While it is technically possible to copy NoteProperty members from one string instance to another (see below), that won't help you, unfortunately:
PowerShell exposes the $PROFILE variable for the user's benefit:
It doesn't use that variable's values internally to determine the profile locations when it loads profiles,
these profile locations are not configurable.
You should treat $PROFILE as a read-only variable, even though it can technically be modified.
For workarounds, see the answer to this related question.
If starting interactive sessions is the focus, the most promising approach is to create a dedicated shortcut file (*.lnk) for launching your PowerShell sessions, defined with a target command such as the following:
powershell -noprofile -noexit -c ". c:\CustomProfileDir\profile.ps1"
To use PowerShell [Core] 6+, substitute pwsh for powershell.
Copying NoteProperty members between objects:
Note:
As stated, this will not solve your problem, but it illustrates how NoteProperty members can be copied from one object to another, even between strings that have different values.
With [string] instances specifically, using -PassThru and assigning back to the input variable ($obj = $obj | Add-Member -PassThru ...) is required in order for the NoteProperty members to be retained; for other types, $obj | Add-Member ... is sufficient.
# Assign the new string value to a new (temporary) variable.
$newProfile = '/path/to/my.ps1'
# Copy the NoteProperty members from the original, decorated string
# to the new variable's string instance.
$PROFILE.psobject.properties | where MemberType -eq 'NoteProperty' | foreach {
$newProfile = $newProfile |
Add-Member -PassThru -NotePropertyName $_.Name -NotePropertyValue $_.Value
}
# You can now assign the new variable to the old one,
# but note that *with $PROFILE that won't help*.
# $PROFILE SHOULD BE TREATED AS READ-ONLY.
$PROFILE = $newProfile
As #mklement0 pointed out, I cannot bypass the functionality of $profile, but I can trick it. Without the below, all shells and scripts take 6.5 to 9 sec to start when I am remotely connected to my corporate VPN (part of that, about 3 seconds is my $profile functions etc). With the below, all consoles and scripts startup in 1 second (and that includes my $profile functions about 1000 lines of code). Putting this solution here in case of help to others.
Running the code at the bottom of this answer results in pushing the following into my $Profile.AllUsersAllHosts which will now always reference a profile at the default C:\ location (and correctly per host type as well - console, ISE, VSCode etc).
Set-Location C:\Users\$($env:Username)
# Prevent loading profile from network share
if ($Profile.CurrentUserCurrentHost -ne "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])") {
$Profile = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])"
if (Test-Path "$Profile") { . $Profile }
}
All I then need to do is delete any profiles on the network share and now I get 1 second load times whether connected remotely to the VPN or not, and whether I open a console or a script etc.
Write-Host ""
Write-Host "Do you want to update `$Profile.AllUsersAllHosts to always redirect to C:\Users\$($env:Username) ?"
Write-Host "This can significantly speed up PowerShell load times when working with a network share over a VPN."
Write-Host "Note that this is just a bypass to always use the *default* profile location on C:\."
Write-Host " `$Profile.AllUsersAllHosts = C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Write-Host " `$Profile.CurrentHostCurrentUser = C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split("\"))[-1]_profile.ps1"
Write-Host ""
$ProfileAdmin = "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
# Use (($Profile).split("\"))[-1] to capture correct prefix PowerShell, VSCode. $ShellId does not work for VSCode etc
$ProfileUser = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split('\')[-1])"
$ProfileUserText = '"C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])"'
Write-Host "Press any key to use `$Profile.AllUsersAllHosts to stop using a profile from a network share"
pause
# - Create $Profile.AllUsersAllHosts
# - Set-Location C:\Users\$($env:Username) (override Admin defaulting into system32)
# - If the system decides that it will use C:\ have to check that so that we do not double-load $Profile!
# - If that condition is ok, set $Profile = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$($ShellId)_profile.ps1"
# - Then just dotsource $Profile as it's set to the C:\ drive as required.
if (!(Test-Path $(Split-Path $ProfileAdmin))) { mkdir $(Split-Path $ProfileAdmin) -Force }
if (!(Test-Path $(Split-Path $ProfileUser))) { mkdir $(Split-Path $ProfileUser) -Force }
Write-Host "`nCreating backup of existing profile ..."
If (Test-Path $ProfileAdmin) { Copy-Item -Path "$($ProfileAdmin)" -Destination "$($ProfileAdmin)_$(Get-Date -format "yyyy-MM-dd__HH-mm-ss").txt" }
Set-Content -Path $ProfileAdmin -Value "Set-Location C:\Users\`$(`$env:Username)"
Add-Content -Path $ProfileAdmin -Value ""
Add-Content -Path $ProfileAdmin -Value "# Prevent loading profile from network share"
Add-Content -Path $ProfileAdmin -Value "if (`$Profile.CurrentUserCurrentHost -ne $ProfileUserText) {"
Add-Content -Path $ProfileAdmin -Value " `$Profile = $ProfileUserText"
Add-Content -Path $ProfileAdmin -Value " if (Test-Path `"`$Profile`") { . `$Profile }"
Add-Content -Path $ProfileAdmin -Value "}"

Combine outputs in Powershell

I currently have this script that checks the registry and if the key exists then it will output a value to the console.
How can I modify this script so that it saves each output to a variable and then that variable will be exported to a text/csv file?
if ((Get-ItemPropertyValue -Path "HKLM:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_HTTP_USERNAME_PASSWORD_DISABLE" -Name HelpPane.exe) -eq '1')
{
Write-Output 'Yes'
}
else
{
Write-Output 'No'
}
if ((Get-ItemPropertyValue -Path "HKLM:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_DISABLE_SQM_UPLOAD_FOR_APP" -Name iexplore.exe) -eq '1')
{
Write-Output 'Yes'
}
else
{
Write-Output 'No'
}
if ($Host.Name -eq "ConsoleHost")
{
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
Use Tee-Object for this, which moves data through the pipeline as well as saves it to a file:
$content | Tee-Object -FilePath C:\some\path\on\disk.txt
This will take your variable $content, pipe it to Tee-Object which writes the output to a file, then takes the same output and pushes it through the pipeline. You should see that $content is also written to the output stream in this case but you could also pass it to another cmdlet in the pipeline if you choose to do so.
You have options.
3 ways to store and display PowerShell Variable simultaneously
https://ridicurious.com/2017/06/30/3-ways-to-store-display-results-infrom-a-powershell-variable-at-the-same-time
# Using -OutVariable parameter
Get-Process a* -OutVariable process
# PowerShell Variable squeezing
($process = Get-Process a*)
# Using Tee-Object Cmdlet
Tee-Object Cmdlet T’s results to o/p stream and Variable $process at the same time
Point of note:
Avoid using Write-Host/echo, unless you are using screen text coloring. There is little reason to use it as output to the screen is the PowerShell default.
Also, if you are planning to use data down the line/ pipe, etc, then Write-Host empties the buffer and the data is gone. Well depending on what version of PowerShell you are using.
Resources:
From the creator of Powershell.
Write-Host Considered Harmful
http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful
... Jeffrey Snover changes his stance on this as of May 2016.
With PowerShell v5 Write-Host no longer "kills puppies". data is
captured into info stream ...
https://twitter.com/jsnover/status/727902887183966208
https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Write-Information?view=powershell-5.1
Your code without the Write-Host thing.
if ((Get-ItemPropertyValue -Path 'HKLM:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_HTTP_USERNAME_PASSWORD_DISABLE' -Name HelpPane.exe) -eq '1')
{'Yes'}
else {'No'}
if ((Get-ItemPropertyValue -Path 'HKLM:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_DISABLE_SQM_UPLOAD_FOR_APP' -Name iexplore.exe) -eq '1')
{'Yes'}
else { 'No'}
if ($Host.Name -eq "ConsoleHost")
{
'Press any key to continue...'
$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp') > $null
}
Lastly, be cognizant about quoting. Single quotes for simple strings, and double quotes for variable expansion or other specific string handling.
As defined in the help files and other resources:
about_Quoting_Rules - PowerShell | Microsoft Docs
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
A Story of PowerShell Quoting Rules
https://trevorsullivan.net/2016/07/20/powershell-quoting
Windows PowerShell Quotes
https://www.computerperformance.co.uk/powershell/quotes

Powershell Get-Variable doesn't retreive the Value but manually writing the Variable does

I am trying to Dynamically Create Sub Variables and assign them values, but since they are dynamically named, they need to be dynamically called and when using the get-variable command I cannot get it to actually show anything....
Function Get-LocalGroups {
param(
[String]$ComputerName
)
Remove-Variable -Scope Script -Name System.LocalGroups.* -Force
$computer = [ADSI]"WinNT://$ComputerName"
$computer.psbase.children | where { $_.psbase.schemaClassName -eq 'group' } | foreach {
$Members = $_.psbase.Invoke("Members") | foreach { ($_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null))}
New-Variable -Scope Script -Name System.LocalGroups.$(($_.name).Replace(" ","-")) -Force
$Script:System.LocalGroups.($_.name.Replace(" ","-")) = $Members
$Members
}
}
$System = #{}
$System.ComputerName = $Env:ComputerName
$System.LocalGroups = #{}
Get-LocalGroups -ComputerName $System.ComputerName
Get-Variable System.LocalGroups.* |ft -AutoSize
$System.LocalGroups.Users
(I found this old unanswered question when searching for information on Get-Variable)
Variable names which include dots
You are creating variables with dots in the name:
New-Variable -Scope Script -Name
System.LocalGroups.$(($_.name).Replace(" ","-")) -Force
Normally dots would be used for properties of that variable/object, rather than creating a heirarchical namespace with the variable names.
If you want to use dots in your variable names, use curly brackets when you reference those variables as per the comments to the OP, for example:
${System.LocalGroups.Users}
This is explained a little more in the answers to a question I asked a few years ago: Do curly braces mean anything in PowerShell variable declaration?
Alternative ideas
Instead of creating variables with New-Variable you could use a collection like a hashtable:
$LocalGroups = #{}
... | foreach {
...
$LocalGroups.[($_.name) -replace(" ","-")] = $Members
}
$LocalGroups.Users
Or you could contstruct a PSCustomObject. I won't try to reproduce the instructions here as there are plenty of examples of how to do this already.