How to get a lengthy string environment variable in full? - json

The command gci env:ApiSecret | ConvertTo-Json works to return a long string, the API secret for Twitter, which is truncated without the pipe to JSON.
However, the JSON is rather spammy.
Is there a "goldilocks" way to get the lengthy string value without the extraneous details?
(Unfortunately, gci env: truncates the key)

Get-ChildItem is for retrieving all or a subset of items from a container. Note that it outputs an object with Name and Value properties (substituting Path as another lengthy environment variable value)...
PS> gci env:Path
Name Value
---- -----
Path C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\WINDO...
Get-Item yields the same result...
PS> gi env:Path
Name Value
---- -----
Path C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\WINDO...
Either way, the object retrieved is a DictionaryEntry...
PS> gi env:Path | Get-Member
TypeName: System.Collections.DictionaryEntry
Name MemberType Definition
---- ---------- ----------
Name AliasProperty Name = Key
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
PSDrive NoteProperty PSDriveInfo PSDrive=Env
PSIsContainer NoteProperty bool PSIsContainer=False
PSPath NoteProperty string PSPath=Microsoft.PowerShell.Core\Environment::path
PSProvider NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\Environment
Key Property System.Object Key {get;set;}
Value Property System.Object Value {get;set;}
...and when you pipe that to ConvertTo-Json it will include all kinds of undesirable properties from that class.
In short, don't use ConvertTo-Json for this. Since you know the exact item you want, just retrieve it directly using variable syntax...
PS> $env:Path
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
Equivalent code using the .NET API would be...
PS> [Environment]::GetEnvironmentVariable('Path')
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
If you really wanted to use a Get-*Item cmdlet you'd just need to specify that it's the Value property you want using property syntax...
PS> (gi env:Path).Value
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
...or Select-Object...
PS> gi env:Path | Select-Object -ExpandProperty 'Value'
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
All of the above commands will output only a [String] containing the entirety of that environment variable value. I inserted trailing ellipses since showing my entire Path value is not useful here; in practice, those commands will output the entire environment variable with no truncation.

The simplest way to inspect the value of environment variables in full is to use the $env:<varName> (namespace variable notation) syntax, which in your case means: $env:ApiSecret (if the variable name contains special characters, enclose everything after the $ in {...}; e.g., ${env:ApiSecret(1)})
That way, environment-variable values (which are invariably strings) that are longer than your terminal's (console's) width simply continue on subsequent lines.
To demonstrate:
# Simulate a long value (200 chars.)
$env:ApiSecret = 'x' * 199 + '!'
# Output the long value
$env:ApiSecret
With an 80-char. wide terminal, you'd see output as follows:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!
If you do want to use Get-Item (or Get-ChildItem, which acts the same in this case), you have two options:
# Format-List shows each property on its own line,
# with values wrapping across multiple lines
Get-Item env:ApiSecret | Format-List
# Format-Table -Wrap causes values to wrap as well.
Get-Item env:ApiSecret | Format-Table -Wrap

Your statement does not strip anything away. However, for console display purpose, it truncate the output that you view in the console.
If you assign the result to a variable or pipe to a file, nothing will be truncated.
Therefore, my assumption on your question is that you want to view the result in the console without the console truncating your stuff there.
For that, you could write the results to the host yourself.
Here's a simple example that do just that.
$envvars = gci env:
$Max = ($envvars.name| Measure-Object -Property length -Maximum).Maximum + 3
$envvars | % {Write-Host $_.name.padright($Max,' ') -ForegroundColor Cyan -NoNewline;Write-Host $_.value}
Result — As you can see, the path variable value is no longer truncated.

Related

Assining cmdlet to variable and printing full, literal value in powershell

I've been trying to assign a cmdlet to a variable and then being able to inspect the variable's value as [it was] in the original cmdlet; as opposed to just executing it or only seeing its usual properties with commands like:
get-variable $seevars | fl *
or:
get-childitem -path variable:\$seevars | fl *
E.g. I create a variable:
$seevars = get-childitem variable: | where-object -property name -match somePrefix*
After a while, I may forget what $seevars contains or may want to modify it and so will want to inspect $seevars, expecting to see the value of :
'get-childitem variable: | where-object -property name -match somePrefix*'
Instead I get, undesirably, its properties in its technical format (I assume); out of which it will be hard for me to reconstruct its original, literal cmdlet.
Is this at all possible?

Set property value on object loaded from json containing comments

When loading an object from a json file one can normally set the value on properties and write the file back out like so:
$manifest = (gc $manifestPath) | ConvertFrom-Json -AsHashtable
$manifest.name = "$($manifest.name)-sxs"
$manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM
But if the source json file contains comments, the object's properties can't be set:
// *******************************************************
// GENERATED FILE - DO NOT EDIT DIRECTLY
// *******************************************************
{
"name": "PublishBuildArtifacts"
}
Running the code above throws an error:
$manifest
id : 1D341BB0-2106-458C-8422-D00BCEA6512A
name : PublishBuildArtifacts
friendlyName : ms-resource:loc.friendlyName
description : ms-resource:loc.description
category : Build
visibility : {Build}
author : Microsoft Corporation
version : #{Major=0; Minor=1; Patch=71}
demands : {}
inputs : {#{name=CopyRoot; type=filePath; label=ms-resource:loc.input.label.CopyRoot; defaultValue=;
required=False; helpMarkDown=Root folder to apply copy patterns to. Empty is the root of the
repo.}, #{name=Contents; type=multiLine; label=ms-resource:loc.input.label.Contents;
defaultValue=; required=True; helpMarkDown=File or folder paths to include as part of the
artifact.}, #{name=ArtifactName; type=string; label=ms-resource:loc.input.label.ArtifactName;
defaultValue=; required=True; helpMarkDown=The name of the artifact to create.},
#{name=ArtifactType; type=pickList; label=ms-resource:loc.input.label.ArtifactType;
defaultValue=; required=True; helpMarkDown=The name of the artifact to create.; options=}…}
instanceNameFormat : Publish Artifact: $(ArtifactName)
execution : #{PowerShell=; Node=}
$manifest.name
PublishBuildArtifacts
$manifest.name = "sxs"
InvalidOperation: The property 'name' cannot be found on this object. Verify that the property exists and can be set.
When I strip the comments, I can overwrite the property.
Is there a way I can coax PowerShell to ignore the comments while loading the json file/convert the object and generate a writable object?
I'm not sure if this is intended, but seems like ConvertFrom-Json is treating the comments on the Json as $null when converting it to an object. This only happens if it's receiving an object[] from pipeline, with a string or multi-line string it works fine.
A simple way to demonstrate this using the exact same Json posted in the question:
$contentAsArray = Get-Content test.json | Where-Object {
-not $_.StartsWith('/')
} | ConvertFrom-Json -AsHashtable
$contentAsArray['name'] = 'hello' # works
Here you can see the differences and the workaround, it is definitely recommended to use -Raw on Get-Content so you're passing a multi-line string to ConvertFrom-Json:
$contentAsString = Get-Content test.json -Raw | ConvertFrom-Json -AsHashtable
$contentAsArray = Get-Content test.json | ConvertFrom-Json -AsHashtable
$contentAsString.PSObject, $contentAsArray.PSObject | Select-Object TypeNames, BaseObject
TypeNames BaseObject
--------- ----------
{System.Collections.Hashtable, System.Object} {name}
{System.Object[], System.Array, System.Object} {$null, System.Collections.Hashtable}
$contentAsArray['name'] # null
$null -eq $contentAsArray[0] # True
$contentAsArray[1]['name'] # PublishBuildArtifacts
$contentAsArray[1]['name'] = 'hello'
$contentAsArray[1]['name'] # hello
Santiago Squarzon's helpful answer shows an effective solution and analyzes the symptom. Let me complement it with some background information.
tl;dr
You're seeing a variation of a known bug, still present as of PowerShell 7.2.1, where a blank line or a single-line comment as the first input object unexpectedly causes $null to be emitted (first) - see GitHub issue #12229.
Using -Raw with Get-Content isn't just a workaround, it is the right - and faster - thing to do when piping a file containing JSON to be parsed as whole to ConvertFrom-Json
For multiple input objects (strings), ConverFrom-Json has a(n unfortunate) heuristic built in that tries to infer whether the multiple strings represent either (a) the lines of a single JSON document or (b) separate, individual JSON documents, each on its own line, as follows:
If the first input string is valid JSON by itself, (b) is assumed, and an object representing the parsed JSON ([pscustomobject] or , with -AsHashtable, [hastable], or an array of either) is output for each input string.
Otherwise, (a) is assumed and all input strings are collected first, in a multi-line string, which is then parsed.
The aforementioned bug is that if the first string is an empty/blank line or a single-line comment[1] (applies to both // ... comments, which are invariably single-line, and /* ... */ if they happen to be single-line), (b) is (justifiably) assumed, but an extraneous $null is emitted before the remaining (non-blank, non-comment) lines are parsed and their object representation(s) are output.
As a result an array is invariably returned, whose first element is $null - which isn't obvious, but results in subtle changes in behavior, as you've experienced:
Notably, attempting to set a property on what is presumed to be a single object then fails, because the fact that an array is being accessed makes the property access an instance of member-access enumeration - implicitly applying property access to all elements of a collection rather than the collection itself - which only works for getting property values - and fails obscurely when setting is attempted - see this answer for details.
A simplified example:
# Sample output hashtable parsed from JSON
$manifest = #{ name = 'foo' }
# This is what you (justifiably) THOUGHT you were doing.
$manifest.name = 'bar' # OK
# Due to the bug, this is what you actually attempted.
($null, $manifest).name = 'bar' # !! FAILS - member-access enumeration doesn't support setting.
As noted above, the resulting error message - The property 'name' cannot be found on this object. ... - is unhelpful, as it doesn't indicate the true cause of the problem.
Improving it would be tricky, however, as the user's intent is inherently ambiguous: the property name may EITHER be a situationally unsuccessful attempt to reference a nonexistent property of the collection itself OR, as in this case, a fundamentally unsupported attempt at setting properties via member-access enumeration.
Conceivably, the following would help if the target object is a collection (enumerable) from PowerShell's perspective: The property 'name' cannot be found on this collection, and setting a collection's elements' properties by member-access enumeration isn't supported.
[1] Note that comments in JSON are supported in PowerShell (Core) v6+ only.

Add windows env variables to json object using powershell

I'm quite new to powershell and just need it for a small task so please excuse my complete and utter ineptitude for the language. I was wondering if it were possible to form a json object based off environment variables and a variable that has already been declared earlier in my script. The variable that was already declared is based off a json config named optionsConfig.json and the contents of that file are here.
{"test1": ["options_size", "options_connection", "options_object"],
"test2":["options_customArgs", "options_noUDP", "options_noName"]}
The purpose of the $Options variable in the code below is to take each element in the list value for the respective test and assume that those elements are environment variables in the system, then find their values and form a dictionary object that will be used in the json.
Here is what I have so far.
# Read the JSON file into a custom object.
$configObj = Get-Content -Raw optionsConfig.json |
ConvertFrom-Json
# Retrieve the environment variables whose
# names are listed in the $env:test property
# as name-value pairs.
Get-Item -Path env:* -Include $configObj.$env:testTool
$Options = Get-Item -Path env:* -Include $configObj.$env:testTool |
% {$hash = #{}} {$hash[$_.Name]=$_.Value} {$hash}
The $Options variable looks like so when converted to json
{
"options_size": "default",
"options_object": "forward open",
"options_connection": "connected"
}
I have a few other environment variable values that I would like to be a part of the json object. Those 3 other environment variables I would like the value of are listed below.
$Env.testTool = "test1"
$Env.RecordName = "Record1"
$Env.Target = "Target1"
How would I construct a powershell statement to get the json object to be formatted like this? -
data = {"test": $Env.testTool, "target": "$Env.Target",
"options": "$Options", "RecordName': "$Env.RecordName"}
The keys are all predefined strings and $Options is the dict object from up above. Am I able to form a Json object like this in powershell and how would it be done? Any help would be appreciated. This appears to be the last step in my struggle with powershell.
Here is what I have done.
$jObj = [ordered]#{test= $Env:testTool}
When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','
When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','
Entries of a hashtable literal (#{ ... } or, in its ordered form, [ordered] #{ ... }) must be separated:
either by newlines (each entry on its own line)
or by ; if placed on the same line.
Thus, the following literals are equivalent:
# Multiline form
#{
test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options
}
# Single-line form; separator is ";"
#{ test= $env:testTool; RecordName= $env:RecordName; Target= $env.Target; options=$Options }
Get-Help about_Hashtables has more information.
$jObj = #{test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options}
$jObj | ConvertTo-Json | Set-Content jsonStuff.json
JsonStuff.json is the new json file for the new json object. This syntax for forming $jObj seems to have done the trick.

Why does ConvertFrom-Json produce an Object which turns into a PSCustomObject after assignment? And how do I shortcut that?

I have a simple json file as follows:
[
{"ClientName": "Test Site 1", "ClientID": "000001"},
{"ClientName": "Test Site 2", "ClientID": "000002"},
{"ClientName": "Test Site 3", "ClientID": "000003"}
]
When I use the following PowerShell command:
ConvertFrom-Json (Get-Content TestSites.json -Raw)
I get back a System.Object[]. This doesn't allow me to pipe the output to another function I have which accepts "ClientName" and "ClientID" parameters.
However, when I assign that object to another variable, like this:
$myobj = ConvertFrom-Json (Get-Content TestSites.json -Raw)
$myobj is actually a System.Management.Automation.PSCustomObject which is capable of being passed to my function.
How can I just pipe the results of the original command without having to assign it to another variable first?
I hope that makes sense.
Your JSON is an array correct? PowerShell will unroll arrays in the pipeline unless you explicity change that behavior. Assuming your JSON is stored in the variable $json as a single string consider the following examples.
ConvertFrom-Json $json | ForEach-Object{$_.gettype().fullname}
System.Object[]
(convertFrom-Json $json) | ForEach-Object{$_.gettype().fullname}
System.Management.Automation.PSCustomObject
System.Management.Automation.PSCustomObject
System.Management.Automation.PSCustomObject
You should be able to wrap the expression in brackets to change the outcome. In the second example it should be sending the 3 objects individually down the pipe. In the first it is being sent as a single object array.
My explanation needs work but I am sure of the cause is how PowerShell deals with arrays and the pipeline. Unrolling being a common word used to describe it.
So depending on your use case you might just be able to wrap the expression in brackets so it gets processed before the pipe to ForEach in my example.
If you have an array such as System.Object[] you could try piping via foreach:
ConvertFrom-Json (Get-Content TestSites.json -Raw) | %{ $_ | your-function }
If you want to pass the whole array down the pipe as-is, you can try adding a comma (aka a unary comma before the variable:
,$myobj | whatever
You can probably see how the latter works by comparing the following:
$myobj | Get-Member # Shows the type of the elements of the array
Get-Member -InputObject $myobj # Shows the type of the array
,$myobj | Get-Member # Shows the type of the array

Foreach-Object make mutable copy of $_ in PowerShell

I want to convert entries from Windows Event log to JSON. But I want to preformat some fields. Using ForEach-Object looks like natural decicion for me, but when I try to change attributes there like this:
Get-EventLog System -Newest 2 | % { $_.EntryType = "$($_.EntryType)" } | ConvertTo-Json
it gives me error:
'EntryType' is a ReadOnly property.
How do I made a writable copy of $_ object, or preformat objects before converting to JSON?
You should be able to use Select-Object to do what you want. Select-Object will create entirely new objects (of type PSCustomObject) that you can customize. You can also limit the properties that you actually want, and you can define your own calculated properties.
See this article for more information about calculated properties.
Get-EventLog System -Newest 2 |
Select-Object Index, Time, Source, InstanceID, #{Name='MyEntryType';Expression={$_.EntryType } } |
ConvertTo-Json