Is it possible to attach an event to a PSObject? - events

Say I've a psobject like this :
$o=New-Object PSObject -Property #{"value"=0}
Add-Member -MemberType ScriptMethod -Name "Sqrt" -Value {
echo "the square root of $($this.value) is $([Math]::Round([Math]::Sqrt($this.value),2))"
} -inputObject $o
Is it possible to attach an event so that the method Sqrt() is executed when the value attribute change ? ie :
PS>$o.value=9
will produce
the square root of 9 is 3
update
As per #Richard answer this is the working recipe :
$o=New-Object PSObject -property #{"_val"=1}
Add-Member -MemberType ScriptMethod -Name "Sqrt" -Value {
write-host "the square root of $($this._val) is $([Math]::Round([Math]::Sqrt($this._val),2))"
} -inputObject $o
Add-Member -MemberType ScriptProperty -Name 'val' -Value{ $this._val } -SecondValue { $this._val=$args[0];$this.Sqrt() } -inputObject $o

Rather than making value a NoteProperty make it a ScriptProperty, this includes defining separate get and set methods that are called rather than directly modifying a field.
$theObject | Add-Member -MemberType ScriptProperty -Name 'Value'
-Value{ $this._value }
-SecondValue { $this._value = $args[0]; $this.Sqrt }
(Value defines the get method, SecondValue the set.)
Note as PowerShell doesn't provide any ability to encapsulate data, the underlying field is still accessible to callers. Coding a custom type in C# (or other .NET language) and use of Add-Type can avoid this, but is unlikely to be worth it unless you really have callers who will not follow the rules.
Second Issue
In a ScriptProperty there is no output pipe (any output is thrown away), so echo (being an alias for Write-Output) won't do anything useful. Replacing it with Write-Host works. In general side effects in a property get or set (including output) are poor practice (there is an expectation of low overhead when using them).

Related

How to add a Environment Scope and Deployment Target Scope to a variable in OctopusDeploy

I have to generate a variable dynamically and set it to the variable list using Octopus Deploy REST API.
I don't know how to set the Environment Scope and Deployment Scope to that variable for different values.
Example - ENV_NAME -> [dev,sit,uat,prod - are values for ENV scope (dev,sit,uat,prod) and roles (x,y,z)] etc
How to set the corresponding values for each scope using Octopus REST API
Below is what I have to set the variable name and values
$variableList = #(
#{
Name = "API_ID"
Value = $api_id
Type = "String"
IsSensitive = $false
}
)
# Get space
$space = (Invoke-RestMethod -Method Get -Uri "$octopusURL/api/spaces/all" -Headers $header) | Where-Object {$_.Name -eq $spaceName}
# Get project
$project = (Invoke-RestMethod -Method Get -Uri "$octopusURL/api/$($space.Id)/projects/all" -Headers $header) | Where-Object {$_.Name -eq $projectName}
# Get project variables
$projectVariables = Invoke-RestMethod -Method Get -Uri "$octopusURL/api/$($space.Id)/variables/$($project.VariableSetId)" -Headers $header
foreach($variable in $variableList){
# Check to see if variable is already present
$variableToUpdate = $projectVariables.Variables | Where-Object {$_.Name -eq $variable.Name}
if ($null -eq $variableToUpdate)
{
# Create new object
$variableToUpdate = New-Object -TypeName PSObject
$variableToUpdate | Add-Member -MemberType NoteProperty -Name "Name" -Value $variable.Name
$variableToUpdate | Add-Member -MemberType NoteProperty -Name "Value" -Value $variable.Value
$variableToUpdate | Add-Member -MemberType NoteProperty -Name "Type" -Value $variable.Type
$variableToUpdate | Add-Member -MemberType NoteProperty -Name "IsSensitive" -Value $variable.IsSensitive
# Add to collection
$projectVariables.Variables += $variableToUpdate
$projectVariables.Variables
}
# Update the value
$variableToUpdate.Value = $variable.Value
}
# Update the collection
Invoke-RestMethod -Method Put -Uri "$octopusURL/api/$($space.Id)/variables/$($project.VariableSetId)" -Headers $header -Body ($projectVariables | ConvertTo-Json -Depth 10)
The OctopusDeploy-Api repo contains many sample scripts.
ModifyOrAddVariableToProject.ps1 does almost exactly what you are attempting to do here.
Two things to note, environment scopes have to be the Id of the environment, not the name as shown here, but roles can just be any string, under the scope type of Roles.
If you are trying to scope a variable to the deployment process, then the scope type is ProcessOwner and the value would be the Project Id, or to scope to a runbook it would be the Runbook Id.

Is it possible to have powershell curl output only the content part? [duplicate]

In PowerShell, how do you get an object's property value by specifying its name (a string)? I want something like the following:
$obj = get-something
# View the object's members:
$obj | gm
# I could retrieve a property by doing so:
write-host $obj.SomeProp
# But for many purposes, I would really want to:
write-host $obj | Get-PropertyByName "SomeProp"
Is there something similar to "Get-PropertyByName" in PowerShell?
Sure
write-host ($obj | Select -ExpandProperty "SomeProp")
Or for that matter:
$obj."SomeProp"
Expanding upon #aquinas:
Get-something | select -ExpandProperty PropertyName
or
Get-something | select -expand PropertyName
or
Get-something | select -exp PropertyName
I made these suggestions for those that might just be looking for a single-line command to obtain some piece of information and wanted to include a real-world example.
In managing Office 365 via PowerShell, here was an example I used to obtain all of the users/groups that had been added to the "BookInPolicy" list:
Get-CalendarProcessing conferenceroom#example.com | Select -expand BookInPolicy
Just using "Select BookInPolicy" was cutting off several members, so thank you for this information!
You can get a property by name using the Select-Object cmdlet and specifying the property name(s) that you're interested in. Note that this doesn't simply return the raw value for that property; instead you get something that still behaves like an object.
[PS]> $property = (Get-Process)[0] | Select-Object -Property Name
[PS]> $property
Name
----
armsvc
[PS]> $property.GetType().FullName
System.Management.Automation.PSCustomObject
In order to use the value for that property, you will still need to identify which property you are after, even if there is only one property:
[PS]> $property.Name
armsvc
[PS]> $property -eq "armsvc"
False
[PS]> $property.Name -eq "armsvc"
True
[PS]> $property.Name.GetType().FullName
System.String
As per other answers here, if you want to use a single property within a string, you need to evaluate the expression (put brackets around it) and prefix with a dollar sign ($) to declare the expression dynamically as a variable to be inserted into the string:
[PS]> "The first process in the list is: $($property.Name)"
The first process in the list is: armsvc
Quite correctly, others have answered this question by recommending the -ExpandProperty parameter for the Select-Object cmdlet. This bypasses some of the headache by returning the value of the property specified, but you will want to use different approaches in different scenarios.
-ExpandProperty <String>
Specifies a property to select, and indicates that an attempt should
be made to expand that property
https://technet.microsoft.com/en-us/library/hh849895.aspx
[PS]> (Get-Process)[0] | Select-Object -ExpandProperty Name
armsvc
powershell variables
Try this :
$obj = #{
SomeProp = "Hello"
}
Write-Host "Property Value is $($obj."SomeProp")"
Here is an alternative way to get an object's property value:
write-host $(get-something).SomeProp
$com1 = new-object PSobject #Task1
$com2 = new-object PSobject #Task1
$com3 = new-object PSobject #Task1
$com1 | add-member noteproperty -name user -value jindpal #Task2
$com1 | add-member noteproperty -name code -value IT01 #Task2
$com1 | add-member scriptmethod ver {[system.Environment]::oSVersion.Version} #Task3
$com2 | add-member noteproperty -name user -value singh #Task2
$com2 | add-member noteproperty -name code -value IT02 #Task2
$com2 | add-member scriptmethod ver {[system.Environment]::oSVersion.Version} #Task3
$com3 | add-member noteproperty -name user -value dhanoa #Task2
$com3 | add-member noteproperty -name code -value IT03 #Task2
$com3 | add-member scriptmethod ver {[system.Environment]::oSVersion.Version} #Task3
$arr += $com1, $com2, $com3 #Task4
write-host "windows version of computer1 is: "$com1.ver() #Task3
write-host "user name of computer1 is: "$com1.user #Task6
write-host "code of computer1 is: "$com1,code #Task5
write-host "windows version of computer2 is: "$com2.ver() #Task3
write-host "user name of computer2 is: "$com2.user #Task6
write-host "windows version of computer3 is: "$com3.ver() #Task3
write-host "user name of computer3 is: "$com1.user #Task6
write-host "code of computer3 is: "$com3,code #Task5
read-host

Automatic select only member in the object list

Is there a way to automatically address/select the only member in a object?
In my case the 3rd level member jobf can vary. But on that level it will always only have one member.
### get aeObject ###
try {
$response = Invoke-RestMethod -Method GET -Uri "$client_id/objects/$($i -replace "#","%23")" -Headers $header
}
catch {
Write-Error -Message "$($_.Exception.Message)"
$Error[0] | Format-List -Force
}
### modify JSON and import it ###
$JSON = $response | ConvertTo-Json -Depth 100 | ConvertFrom-Json
$JSON.data
if ($JSON.data.jobf.general_attributes.time_zone) {
$JSON.data.jobf.general_attributes.time_zone = "$TZ"
}
else {
$JSON.data.jobf.general_attributes | Add-Member -Name "time_zone" -MemberType NoteProperty -Value "$TZ"
}
enter image description here
You can use Get-Member to iterate the members at the third level, then filter out the noise and select the name of the first (and only one) member.
# Get the third level single item (eg: jobf, jobx, etc)
$Job = ($JSON.data | Get-Member -MemberType NoteProperty | Select-Object -First 1).Name
# Instead of "jobf", we use the value stored in $Job
if ($JSON.data.$Job.general_attributes.time_zone) {
$JSON.data.$Job.general_attributes.time_zone = "$TZ"
}
else {
$JSON.data.$Job.general_attributes | Add-Member -Name "time_zone" -MemberType NoteProperty -Value "$TZ"
}
References
Get-Member - Gets the properties and methods of objects.

get-childitem variable return empty value

I'm trying to figure out why, my Get-ChilddItem variable is returning an empty $null value while running the below scriptblock, whereas from ISE, if i enter variables values manually, i'm able to get an output of the .sql files I've selected before the Get-ChildItem command. Below is a beginning of the function i'm writing:
Function test{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True]
[string]$client
)
# Define variables
$Master = 'master'
$Slave = 'slave'
$SourcePath = "C:\somefolder\folder"
$Masterfolder = "C:\somemasterfolder"
$Slavefolder = "C:\someslavefolder"
# Create folders
$clientdir2 = New-Item -Path $Slavefolder -name "$client" -ItemType Directory
# Create folder on secondary server
$clientdir = New-Item -Path "Microsoft.PowerShell.Core\FileSystem::\\$master\somemasterfolder" -name "$client" -ItemType Directory
# Getting all sql files and copy them to the destination folder
$gflz = Get-ChildItem $SourcePath\* -Include __07*, __08*, __10*
# Copy
Copy-Item $gflz $clientdir2
# Replace values in each sql files
$cpz = #(Get-ChildItem $clientdir2\* -Include *.sql)
if ($cpz -ne $null) {
Foreach ($flz in $cpz) {
(Get-Content $flz.FullName) | ForEach-Object { $_ -replace'clientID', "$client" -replace 'C:\LSpath', `
"$Masterfolder" -replace'C:\LSPath2', "$Slavefolder" } | Set-Content $flz.FullName}}
Thus, the replace operation cannot take place, as it loops on nothing.
Am I writing this the wrong way for the intended purpose?
Would you point me out to the right direction?
Thanks!
Figured out my problem from this Stackoverflow post PowerShell Script to Find and Replace for all Files with a Specific Extension.
I was pretty sure that the files property in the Get-Content command included Fullname or Name as member. Turns out that when I checked for the members i could not get any properties match FullName or Name:
Get-Content $sqlfiles | gm -MemberType Properties
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
PSChildName NoteProperty System.String
PSChildName=Mysqlfiles.sql
PSDrive NoteProperty System.Management.Automation.PSDriveInfo PSDrive=C
PSParentPath NoteProperty System.String
PSParentPath=C:\parentpath
PSPath NoteProperty System.String
PSPath=mysqlfilefullpathname.sql
PSProvider NoteProperty System.Management.Automation.ProviderInfo
PSProvider=Microsoft.PowerShell.Core\FileSystem
ReadCount NoteProperty System.Int64 ReadCount=1
Length Property int Length {get;}
So the only properties that relates to FullName property was PSPath as suggested in the post that i quoted above.
Defining the right properties turns out to help replace the string inside each files as i was expecting to do.
Credits to Daniel Liuzzi and Robben_Ford_Fan_boy for the hints.

PowerShell Could Not Find Item - Path With Spaces IOException

# ---------------------------------------------------------
# ScriptingGamesBeginnerEvent8_PS1.ps1
# ed wilson, msft 8/21/2009
# PS1 version of HSG-08-19-09 http://bit.ly/1d8Rww
#
# ---------------------------------------------------------
Param(
[string]$path = 'C:\',
[int]$first = 50
)# end param
# *** Function Here ***
function Get-DirSize ($path){
BEGIN {}
PROCESS{
$size = 0
$folders = #()
foreach ($file in (Get-ChildItem $path -Force -ea SilentlyContinue)) {
if ($file.PSIsContainer) {
$subfolders = #(Get-DirSize $file.FullName)
$size += $subfolders[-1].Size
$folders += $subfolders
} else {
$size += $file.Length
}
}
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name Folder -Value (Get-Item $path).fullname
$object | Add-Member -MemberType NoteProperty -Name Size -Value $size
$folders += $object
Write-Output $folders
}
END {}
} # end function Get-DirSize
Function Get-FormattedNumber($size)
{
IF($size -ge 1GB)
{
"{0:n2}" -f ($size / 1GB) + " GigaBytes"
}
ELSEIF($size -ge 1MB)
{
"{0:n2}" -f ($size / 1MB) + " MegaBytes"
}
ELSE
{
"{0:n2}" -f ($size / 1KB) + " KiloBytes"
}
} #end function Get-FormattedNumber
# *** Entry Point to Script ***
if(-not(Test-Path -Path $path))
{
Write-Host -ForegroundColor red "Unable to locate $path"
Help $MyInvocation.InvocationName -full
exit
}
Get-DirSize -path $path |
Sort-Object -Property size -Descending |
Select-Object -Property folder, size -First $first |
Format-Table -Property Folder,
#{ Label="Size of Folder" ; Expression = {Get-FormattedNumber($_.size)} }
So I have this script which I got from
http://gallery.technet.microsoft.com/scriptcenter/36bf0988-867f-45be-92c0-f9b24bd766fb#content
I've been playing around with it and created a batch file to help handle the log output of this file and such. However, I'm noticing that paths with spaces in them don't get read. For example ..Documents\My Music
Get-Item : Could not find item C:\Users\MyUser\Documents\My Music.
At C:\test.ps1:32 char:80
+ $object | Add-Member -MemberType NoteProperty -Name Folder -Value (Get-It
em <<<< $path).fullname
+ CategoryInfo : ObjectNotFound: (C:\Users\MyUser\Documents\My
Music:String) [Get-Item], IOException
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetIt
emCommand
On the TechNet page for the code, someone brings the issue up but no solution is given. I'm not sure how to fix it here. I've played with the $path argument, surrounding it in " " or ' ' and such.
Here is part of the batch file to execute it:
C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -noe -command "& 'C:\test.ps1' -path "'C:\Users\MyUser\'""
Might be a bit late for answer here, but, as Aaron mentioned, this is not due to spaces in the path.
If you read the documentation for Get-Item cmdlet, there is a -Force switch, which allows the cmdlet to get items that cannot otherwise be accessed, such as hidden items.
Moreover, it seems from your code that you are not expecting to pass a wildcard pattern to the cmdlet, so instead of (Get-Item $path).FullName you should use
(Get-Item -force -LiteralPath $path).FullName
That should resolve this issue.
It's not the spaces in the path. If it was, the error would say path C:\Users\MyUser\Documents\My couldn't be found. Get-ChildItem and Get-Item behave... strangely... with certain files/directories, returning errors like you're seeing. That's why Get-ChildItem has an -ErrorAction SilentlyContinue parameter on it. I would add the same to the call to Get-Item, i.e. change
(Get-Item $path).FullName
to
(Get-Item $path -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
or even forgo the call to Get-Item completely:
$path
As suggested by TheTrowser in a comment above: The problem may be resolved if you replace the double-quotes with single quotes surrounding the file directory with spaces. This is what solved it for me.
Using the command below didn't work for me.
get-item 'some path with two spaces.txt'
Enclosing the filename in double quotes within the single quotes, forces Powershell to use the filename as written.
get-item '"some path with two spaces.txt"'
Note: I'm totally cringing at my origal message (cleaned up a bit above). Below is a better example of what I was seeing.
$exampleA = "c:\temp\weird path\blah.txt"
$exampleB = "c:\temp\normal path\blah.txt"
# Works
get-item '$exampleA'
get-item $exampleB
# Fails
get-item $exampleA

Resources