i have a powershell listener running on my windows-box. Code from Powershell-Gallery: https://www.powershellgallery.com/packages/HttpListener/1.0.2
The "Client" calls the listener with the following example url:
With Powershell i do:
invoke-webrequest -Uri "http://127.0.0.1:8888/Test?id='1234'¶m2='##$$'¶m3='This is a test!'"
I have no idea, how to drop the parameters from the url to variables with the same name in powershell. I need to bring the parameters to powershellvariables to simply echo them. this is my last missing part. The parameters are separated with & and parameternames are case-sensitive.
To be more detailed, the id from the url should be in a powershell-variable named $id with the value 1234. The Variables can contain spaces, special characters, numbers, alphas. They are case sensitive. The parametervalue could be "My Name is "Anna"! My Pets Name is 'Bello'. " with all the "dirty" Characters like '%$"!{[().
Can someone point me to the right way how to get this solved?
In a valid url, the $ characters should have been escaped to %24
The other 'dirty' character % in Url escaped form is %25
This means the example url is invalid and should be:
$url = "http://127.0.0.1:8888/Test?id='1234'¶m2='##%24%24'¶m3='This is a test!'"
Then the following does work
$url = "http://127.0.0.1:8888/Test?id='1234'¶m2='##%24%24'¶m3='This is a test!'"
if ($url -is [uri]) {
$url = $url.ToString()
}
# test if the url has a query string
if ($url.IndexOf('?') -ge 0) {
# get the part of the url after the question mark to get the query string
$query = ($url -split '\?')[1]
# or use: $query = $url.Substring($url.IndexOf('?') + 1)
# remove possible fragment part of the query string
$query = $query.Split('#')[0]
# detect variable names and their values in the query string
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$varName = [uri]::UnescapeDataString($kv[0]).Trim()
$varValue = [uri]::UnescapeDataString($kv[1])
New-Variable -Name $varname -Value $varValue -Force
}
}
else {
Write-Warning "No query string found as part of the given URL"
}
Prove it by writing the newly created variables to the console
Write-Host "`$id = $id"
Write-Host "`$param2 = $param2"
Write-Host "`$param3 = $param3"
which in this example would print
$id = '1234'
$param2 = '##$$'
$param3 = 'This is a test!'
However, I personally would not like to create variables like this, because of the risk of overwriting already existing ones.
I think it would be better to store them in a hash like this:
# detect variable names and their values in the query string
# and store them in a Hashtable
$queryHash = #{}
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$name = [uri]::UnescapeDataString($kv[0]).Trim()
$queryHash[$name] = [uri]::UnescapeDataString($kv[1])
}
$queryHash
which outputs
Name Value
---- -----
id '1234'
param2 '##$$'
param3 'This is a test!'
Related
I have the following code
function Get($url) {
$x = Invoke-RestMethod $url
# ....
$result # return a json which some values (id, name, color) and a list
}
Get "http://...." |
% {
$id = $_.id
$url = "${$_.url}/xxx"
Get $ur |
% {
$name = $_.name
$url = $_.url
Get $url |
% {
$color = $_.color
$url = $_.url
Get $url | % {
# other layer omitted
}
}
}
}
The code looks bad. It seems the embedded layers should be resolved by monad. Is there a way to do it in PowerShell?
The following shows a pseudo F# code.
myMonad {
let! (id1, _, _, urls1, _ ) = Get ["http://..."]
let! (_, name2, _, urls2, _ ) = Get urls1
let! (_, _, color3, urls3, names) = Get urls2
// ....
printfn "%d %s %s %A" id1, name2, color3, names
}
PowerShell is clearly not a function language, though it has some functional traits and its many constructs and dynamic nature allow you to emulate functional features:
The code below defines a recurse-and-aggregate function named recurse that can invoke your Get function as follows in order to aggregate properties Id, Name, and Color from recursive calls to Get via each returned object's .URL property:
recurse -Function Get <# target function #> `
-Argument http://example.org <# input URL #> `
-RecurseOn URL <# what result property to recurse on #> `
-PropertyNames Id, Name, Color <# what properties to aggregate successively,
in each iteration #>
recurse source code with sample code:
# A fairly generic recurse-and-aggregate function that aggregates properties from
# result objects from recursive calls to a given function based on a single result property.
function recurse($Function, $Arguments, $RecurseOn, $PropertyNames, $outProperties = [ordered] #{ }) {
# Call the target function with the specified arguments.
$result = & $Function $Arguments
# Split into the names of the current and the remaining properties to aggregate.
$propName, $remainingPropNames = $PropertyNames
# Add the value of the current property of interest to the output hashtable, if present.
if ($null -ne $result.$propName) {
if ($outProperties.Contains($propName)) { # not the first value
if ($outProperties.$propName -is [array]) { # already an array -> "extend" the array.
$outProperties.$propName += $result.$propName
}
else { # not an array yet -> convert to array, with the previous value and the new one as the elements.
$outProperties.$propName = $outProperties.$propName, $result.$propName
}
}
else { # first value -> assign as-is
$outProperties.$propName = $result.$propName
}
}
if ($remainingPropNames) {
# Recurse on the value(s) of the property specfied with -RecurseOn
foreach ($newArgument in $result.$RecurseOn) {
$null = recurse -Function $function -Argument $newArgument -RecurseOn $RecurseOn -PropertyNames $remainingPropNames -outProperties $outProperties
}
}
# Return the aggregated properties.
$outProperties
}
# Sample input function:
# It makes a REST call to the given URL and returns the
# result object.
function Get($url) {
Write-Host "Get $url"
$id = 1
$name = ''
$color = ''
if ($url -eq 'http://example.org') {
$urls = 'http://example.org/1', 'http://example.org/2'
$id = 1
}
elseif ($url -match 'http://example.org/\d$') {
$urls = "$url/a", "$url/b"
$name = 'test'
}
elseif ($url -match 'http://example.org/\d/a') {
$urls = '...'
$name = 'test'
$color = "[color of $url] #1", "[color of $url] #2"
}
elseif ($url -match 'http://example.org/\d/b') {
$urls = '...'
$name = 'test'
$color = "[color of $url] #1", "[color of $url] #2"
}
[pscustomobject] #{
URL = $urls
Id = $id
Name = $name
Color = $color
}
}
# Call the recurse-and-aggregate function, passing it the name of the Get()
# function, an input URL, what result property to recurse on, and a list of properties to aggregate.
# The output is an ordered hashtable containing the aggregated property values.
recurse -Function Get <# target function #> `
-Argument http://example.org <# input URL #> `
-RecurseOn URL <# what result property to recurse on #> `
-PropertyNames Id, Name, Color <# what properties to aggregate successively,
in each iteration #>
The above yields:
Name Value
---- -----
Id 1
Name {test, test}
Color {[color of http://example.org/1/a] #1, [color of http://example.org/1/a] #2, [color of http://example.org/1/b] #1, [color of htt…
Well, you could insert F# code into Powershell with Add-Type. See Example 7: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-5.1
I need to update ini configuration file. I managed to convert the file to hastable and updating values. But when I check if the changes are correct in the file, it hasn't changed. Add-Content doesn't work. do I need to convert to String to use Add-Content function?
Configuration file is filled with plain text also.
"ini" Configuration file:
[sqlScript1Deployment]
sqlServerName = '??????????'
olapServerName = '??????????'
(...)
My ps1 code:
[hashtable]$ht = Get-Configuration($iniFilepath)
$ht["sqlScript1Deployment"]["sqlServerName"] = 'Master'
$ht | Add-Content $iniFilepath
Expected code in "ini" file:
[sqlScript1Deployment]
sqlServerName = 'Master'
Actual result in "ini" file:
[sqlScript1Deployment]
sqlServerName = '??????????'
I have no idea where you got the Get-Configuration function from, but if it creates a hashtable where each Key is a Section for the INI and every Value is a name/value pair like this:
$ht = #{
'sqlScript1Deployment' = #{
'sqlServerName' = '??????????'
'olapServerName' = '??????????'
}
}
The following code may help:
# set the new value for sqlServerName
$ht['sqlScript1Deployment']['sqlServerName'] = 'Master'
# write the Hashtable back to disk as .INI file
$sb = New-Object -TypeName System.Text.StringBuilder
# the Keys are the Sections in the Ini file
# the properties are name/value pairs within these keys
foreach ($section in $ht.Keys) {
[void]$sb.AppendLine("[$section]")
foreach ($name in $ht[$section].Keys) {
$value = $ht[$section][$name]
# the value needs to be quoted when:
# - it begins or ends with whitespace characters
# - it contains single or double quote characters
# - it contains possible comment characters ('#' or ';')
if ($value -match '^\s+|[#;"'']|\s+$') {
# escape quotes inside the value and surround the value with double quote marks
$value = '"' + ($value -replace '(["''])', '\$1') + '"'
}
[void]$sb.AppendLine("$name = $value")
}
}
$sb.ToString() | Out-File $iniFilepath
[void]$sb.Clear()
The resulting file will look like this:
[sqlScript1Deployment]
sqlServerName = Master
olapServerName = ??????????
In a PowerShell script that I'm creating, I want to output specific commands with the parameter values being passed. The output could go to a log file and/or the console output. The following will output what I want to the console but I have to duplicate the line of script of interest and at some point a subtle mistake will be made where the commands don't match. I've tried Set-PSDebug and Trace-Command and neither give the result I seek. I've thought about putting the line of script into a string, writing it out, and then calling Invoke-Expression but I'll give up autocompletion/intellisense.
Example with duplicated line for writing and execution:
Write-Output "New-AzureRmResourceGroup -Name $rgFullName -Location $location -Tag $tags -Force"
New-AzureRmResourceGroup -Name $rgFullName -Location $location -Tag $tags -Force
Output result with expanded variables. $tags didn't expand to actual hashtable values:
New-AzureRmResourceGroup -Name StorageAccounts -Location West US -Tag System.Collections.Hashtable -Force
What other options or commandlets can I use to achieve the tracing without writing duplicated code and maybe even expanding the hashtable?
There is no built-in feature I'm aware of that echoes a version of a command being executed with variables and expressions used in arguments expanded.
Even if there were, it would only work faithfully in simple cases, because not all objects have literal representations.
However, with limitations, you can roll your own solution, based on &, the call operator, and parameter splatting via a hashtable of argument values defined up front:
# Sample argument values.
$rgFullName = 'full name'
$location = 'loc'
$tags = #{ one = 1; two = 2; three = 3 }
# Define the command to execute:
# * as a string variable that contains the command name / path
# * as a hashtable that defines the arguments to pass via
# splatting (see below.)
$command = 'New-AzureRmResourceGroup'
$commandArgs = [ordered] #{
Name = $rgFullName
Location = $location
Tag = $tags
Force = $True
}
# Echo the command to be executed.
$command, $commandArgs
# Execute the command, using & and splatting (note the '#' instead of '$')
& $command #commandArgs
The above echoes the following (excluding any output from the actual execution):
New-AzureRmResourceGroup
Name Value
---- -----
Name full name
Location loc
Tag {two, three, one}
Force True
As you can see:
PowerShell's default output formatting results in a multi-line representation of the hashtable used for splatting.
The $tags entry, a hashtable itself, is unfortunately only represented by its keys - the values are missing.
However, you can customize the output programmatically to create a single-line representation that approximates the command with expanded arguments, including showing hashtables with their values, using helper function convertTo-PseudoCommandLine:
# Helper function that converts a command name and its arguments specified
# via a hashtable or array into a pseudo-command line string that
# *approximates* the command using literal values.
# Main use is for logging, to reflect commands with their expanded arguments.
function convertTo-PseudoCommandLine ($commandName, $commandArgs) {
# Helper script block that transforms a single parameter-name/value pair
# into part of a command line.
$sbToCmdLineArg = { param($paramName, $arg)
$argTransformed = ''; $sep = ' '
if ($arg -is [Collections.IDictionary]) { # hashtable
$argTransformed = '#{{{0}}}' -f ($(foreach ($key in $arg.Keys) { '{0}={1}' -f (& $sbToCmdLineArg '' $key), (& $sbToCmdLineArg '' $arg[$key]) }) -join ';')
} elseif ($arg -is [Collections.ICollection]) { # array / collection
$argTransformed = $(foreach ($el in $arg) { & $sbToCmdLineArg $el }) -join ','
}
elseif ($arg -is [bool]) { # assume it is a switch
$argTransformed = ('$False', '$True')[$arg]
$sep = ':' # passing an argument to a switch requires -switch:<val> format
} elseif ($arg -match '^[$#(]|\s|"') {
$argTransformed = "'{0}'" -f ($arg -replace "'", "''") # single-quote and escape embedded single quotes
} else {
$argTransformed = "$arg" # stringify as is - no quoting needed
}
if ($paramName) { # a parameter-argument pair
'-{0}{1}{2}' -f $paramName, $sep, $argTransformed
} else { # the command name or a hashtable key or value
$argTransformed
}
}
# Synthesize and output the pseudo-command line.
$cmdLine = (& $sbToCmdLineArg '' $commandName)
if ($commandArgs -is [Collections.IDictionary]) { # hashtable
$cmdLine += ' ' +
$(foreach ($param in $commandArgs.Keys) { & $sbToCmdLineArg $param $commandArgs[$param] }) -join ' '
} elseif ($commandArgs) { # array / other collection
$cmdLine += ' ' +
$(foreach ($arg in $commandArgs) { & $sbToCmdLineArg '' $arg }) -join ' '
}
# Output the command line.
# If the comamnd name ended up quoted, we must prepend '& '
if ($cmdLine[0] -eq "'") {
"& $cmdLine"
} else {
$cmdLine
}
}
With convertTo-PseudoCommandLine defined (before or above the code below), you can then use:
# Sample argument values.
$rgFullName = 'full name'
$location = 'loc'
$tags = #{ one = 1; two = 2; three = 3 }
# Define the command to execute:
# * as a string variable that contains the command name / path
# * as a hashtable that defines the arguments to pass via
# splatting (see below.)
$command = 'New-AzureRmResourceGroup'
$commandArgs = [ordered] #{
Name = $rgFullName
Location = $location
Tag = $tags
Force = $True
}
# Echo the command to be executed as a pseud-command line
# created by the helper function.
convertTo-PseudoCommandLine $command $commandArgs
# Execute the command, using & and splatting (note the '#' instead of '$')
& $command #commandArgs
This yields (excluding any output from the actual execution):
New-AzureRmResourceGroup -Name 'full name' -Location loc -Tag #{two=2;three=3;one=1} -Force:$True
I'm trying to create a script, where the variables are outside of the PowerShell script. It's just a text file. In that file, we define server name, group names and their values. Group names meaning that the host has these strings in their hostname.
Hostnames have the higher priority, and Group names have the least priority. If neither is present, then there is a default value (inside the ps script).
The following elseif condition isn't working for me and I've tried a lot of other ways but could not get it working.
elseif ($MachineName.Contains("$var.key")) { # Doesn't work. What can be used here?
$foldername = ("$var.value") # Doesn't work. What can be used here?
}
Content of vars.txt:
#Groups
DB=folder7
#Hostnames
server_JIRA_001=folder3
server_DB_001=folder5
server_DB_005=folder6
If a hostname is server_DB_006 it will use value "folder7" as it has the "DB" in their hostname, but if the hostname is server_DB_005 then it will use value "folder6"
Content of script.ps1
$MachineName = "server_DB_006"
$var = (Get-Content "c:\Users\user\Desktop\vars.txt" -Raw | ConvertFrom-StringData)
if ($var.$MachineName -ne $null) {
# if hostname exists, then use its var.value
$foldername = ($var.$MachineName) # returns value of $var.hostname
} elseif ($MachineName.Contains("$var.key")) { # Doesn't work. What can be used here?
# if no hostname defined, then search for group name and use its var.value
$foldername = ("$var.value") # Doesn't work. What can be used here?
} else {
foldername = "default_folder"
}
So you can create a function who will bring back the key name if a string contains it. It loops through the keys and checks the $Text to see if it contains the key text else returns false.
function StringContainsHashTableKey(){
PARAM(
[string]$Text,
[hashtable]$HashTable
)
foreach($i in $Hashtable.Keys.GetEnumerator()){
if($Text -like "*$i*" ){
return $i
}
}
return $false
}
$Var = Get-Content "c:\Users\user\Desktop\vars.txt" -Raw | ConvertFrom-StringData
$MachineName = "server_DB_006"
$KeyMatch = StringContainsHashTableKey -Text $MachineName -HashTable $Var
if ($var.$MachineName -ne $null) { #if hostname exists, then use its var.value
$foldername = ($var.$MachineName) # returns value of $var.hostname
}
# If no hostname defined, then search for group name and use its var.value
Elseif ($KeyMatch -ne $false) { # Doesn't work. What can be used here?
$foldername = $var.$KeyMatch
}
Else {
$foldername = "default_folder"
}
$foldername
I have a few URLs which would need to cut and separate the first part of the each URL, i.e example1.com, example2.com, example3.com from each line and store in a variable
Contents in url.csv
https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4
https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8
https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6
Script:
$oldurl = Import-CSV "url.csv"
$newurl = $oldurl.list -replace "https://"
This would replace https://, however the rest of each cannot be hard coded as those values can change.
What could be change code change required to cut anything from and after /v1/ along with https://?
$list = #(
"https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4",
"https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8",
"https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6"
)
$result = $list | %{
$uri = [System.Uri] $_
$uri.Authority
}
$result
See System.Uri properties to potentially assemble the information you need in your result list.
This will cut off anything after "/v1/" and it self. Is that what you want?
$string = "https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4"
$string = $string -replace "https://"
$pos = $string.IndexOf("/v1/")
$result = $string.Substring(0, $pos)
$result
Output: example1.com