How to insert PowerShell variables into a string block - powershell

Quick question, i'm trying to insert a variable into a string json body block like below, but when i perform a rest api call by passing the json body into the invoke-webrequest function, the variable is actually not getting inserted. In the alert software i'm using i just see the message as 'The following host $($scrapperHost) is not running!!'
#variable
$myHost = $Env:Computername
#variable prints the correct hostname
#string json body
$jsonBody = #'
{
"message": "The following host $($myHost) is not running!!"}
}

There are several ways you can insert variables into strings / Here Strings.
For your specific example, instead of using #' '# you can use #" "#, and you can insert the variable without the $ at the beginning:
#variable
$myHost = $Env:Computername
#variable prints the correct hostname
#string json body
$jsonBody = #"
{
"message": "The following host ${myHost} is not running!!"}
}
"#
This would also work fine, always using #" "#:
"message": "The following host $myHost is not running!!"
Or:
# This is very useful when you want to insert a property of your variable
"message": "The following host $($myHost) is not running!!"`
Another example:
$jsonBody = #'
{{
"message": "The following host {0} is not running!!"
}}
'# -f $myHost
Notice I'm using double {{ }} because you need to escape, basically you're telling Powershell you want a literal curly brace instead of using the curly brace as a special character.
There are a lot more examples you can use to insert variables into strings, check out this post by Kevin Marquette: https://powershellexplained.com/2017-01-13-powershell-variable-substitution-in-strings/

Related

PowerShell Classifier with File Server Resource Manager

I'm trying to use the Windows PowerShell Classifier in FSRM on Server 2019. I need it to look for files that start with "~$" and classify them with a Yes or No property I created.
I would also be fine with a REGEX code as well.
This is what I have but it's not working:
# Global variables available:
# $ModuleDefinition (IFsrmPipelineModuleDefinition)
# $Rule (IFsrmClassificationRule)
# $PropertyDefinition (IFsrmPropertyDefinition)
#
# And (optionally) any parameters you provide in the Script parameters box below,
# i.e. "$a = 1; $b = 2" . The string you enter is treated as a script and executed so the
# variables you define become globally available
# optional function to specify when the behavior of this script was last modified
# if it consumes additional files. emit one value of type DateTime
#
# function LastModified
# {
# }
# required function that outputs a value to be assigned to the specified property for each file classified
# emitting no value is allowed, which causes no value to be assigned for the property
# emitting more than one value will result in errors during classification
# begin and end are optional; process is required
#
function GetPropertyValueToApply
{
# this parameter is of type IFsrmPropertyBag
# it also has an additional method, GetStream, which returns a IO.Stream object to use for
# reading the contents of the file. Make sure to close the stream after you are done reading
# from the file
param
(
[Parameter(Position = 0)] $PropertyBag
)
Process
{
$FileName = $_.Name
If($FileName -like "~$*")
{
$True
}
Else
{
$False
}
}

powershell get specific row from a body

i'm getting some problem to get a specific row from a powershell body. HR team send email to us, a logic app get the body of email and pass it to a script that disable the user.
The body is like this:
Hi,
the following user have to be dismiss:
#mail: user1#mycompany.com
#mail: user2#mycompany.com
#mail: user2#mycompany.com
Bye
hr team
hrteam#mycompany.com
i would like to get only the specific row:
#mail: user1#mycompany.com
#mail: user2#mycompany.com
#mail: user2#mycompany.com
to do this i did a trick with following code:
$Body
$SplitBody = $Body.split("").split(" ").Split("#")
$objetbody = $SplitBody.Replace(" ","").Trim()
and i got following result:
Hi,
the
following
user
have
to
be
dismiss:
mail:
user1#mycompany.com
mail:
user2#mycompany.com
mail:
user2#mycompany.com
Bye
hr
team
hrteam#mycompany.com
after that i pass $objetbody into a foreach and loop all row(at the end of this foreach is put a break becouse it can disable HR mail). The flow work if HR sent only 1 mail to disable.
My question is there is a way to got that specific row that contains the mails?
Thanks
Here is a one-liner to output all email addresses using the .NET RegEx class:
[RegEx]::Matches( $body, '(?<=#mail:\s*)\S+' ).Value
The same can be achieved using Select-String:
($body | Select-String '(?<=#mail:\s*)\S+' -AllMatches).Matches.Value
RegEx breakdown:
(?<= ... starts a positive look behind pattern, meaning that the sub string we search for must be preceded by the given pattern, but this pattern won't be included in the result
#mail:\s* ... literal "#mail:" followed by optional whitespace
) ... ends the look behind pattern
\S+ ... any sequence of non-whitespace characters -> the email address
I kept the email address pattern simple, because correctly matching email addresses using RegEx can be hard. Anyway you propably want to do validation as a separate step so you can report invalid addresses. This can be done much simpler using the .NET MailAddress class:
$mailAddresses = [RegEx]::Matches( $body, '(?<=#mail:\s*)\S+' ).Value
foreach( $adr in $mailAddresses) {
try {
[System.Net.Mail.MailAddress]::new( $adr )
} catch {
# The MailAddress constructor throws FormatException for invalid address
Write-Error "Invalid mail address: '$adr'"
}
}
You could use a loop in addition to the -match operator and the use of the automatic variable $Matches:
foreach($line in $Body)
{
if($line -match '^#mail: (.*#.*\.[\w\d]+)')
{
$Matches[1]
}
}
From the example above, $Matches[1] would return the values for the matched capturing group:
user1#mycompany.com
user2#mycompany.com
user2#mycompany.com
If you want to keep #mail: use $Matches[0] instead.

Import from strings from a text file and only expand variables [duplicate]

I'm trying to write a function that will print a user-supplied greeting addressed to a user-supplied name. I want to use expanding strings the way I can in this code block:
$Name = "World"
$Greeting = "Hello, $Name!"
$Greeting
Which successfully prints Hello, World!. However, when I try to pass these strings as parameters to a function like so,
function HelloWorld
{
Param ($Greeting, $Name)
$Greeting
}
HelloWorld("Hello, $Name!", "World")
I get the output
Hello, !
World
Upon investigation, Powershell seems to be ignoring $Name in "Hello, $Name!" completely, as running
HelloWorld("Hello, !", "World")
Produces output identical to above. Additionally, it doesn't seem to regard "World" as the value of $Name, since running
function HelloWorld
{
Param ($Greeting, $Name)
$Name
}
HelloWorld("Hello, $Name!", "World")
Produces no output.
Is there a way to get the expanding string to work when passed in as a function parameter?
In order to delay string interpolation and perform it on demand, with then-current values, you must use $ExecutionContext.InvokeCommand.ExpandString()[1] on a single-quoted string that acts as a template:
function HelloWorld
{
Param ($Greeting, $Name)
$ExecutionContext.InvokeCommand.ExpandString($Greeting)
}
HelloWorld 'Hello, $Name!' 'World' # -> 'Hello, World!'
Note how 'Hello, $Name!' is single-quoted to prevent instant expansion (interpolation).
Also note how HelloWorld is called with its arguments separated with spaces, not ,, and without (...).
In PowerShell, functions are invoked like command-line executables - foo arg1 arg2 - not like C# methods - foo(arg1, arg2) - see Get-Help about_Parsing.
If you accidentally use , to separate your arguments, you'll construct an array that a function sees as a single argument.
To help you avoid accidental use of method syntax, you can use Set-StrictMode -Version 2 or higher, but note that that entails additional strictness checks.
Note that since PowerShell functions by default also see variables defined in the parent scope (all ancestral scopes), you could simply define any variables that the template references in the calling scope instead of declaring individual parameters such as $Name:
function HelloWorld
{
Param ($Greeting) # Pass the template only.
$ExecutionContext.InvokeCommand.ExpandString($Greeting)
}
$Name = 'World' # Define the variable(s) used in the template.
HelloWorld 'Hello, $Name!' # -> 'Hello, World!'
Caveat: PowerShell string interpolation supports full commands - e.g., "Today is $(Get-Date)" - so unless you fully control or trust the template string, this technique can be security risk.
Ansgar Wiechers proposes a safe alternative based on .NET string formatting via PowerShell's -f operator and indexed placeholders ({0}, {1}, ...):
Note that you can then no longer apply transformations on the arguments as part of the template string or embed commands in it in general.
function HelloWorld
{
Param ($Greeting, $Name)
$Greeting -f $Name
}
HelloWorld 'Hello, {0}!' 'World' # -> 'Hello, World!'
Pitfalls:
PowerShell's string expansion uses the invariant culture, whereas the -f operator performs culture-sensitive formatting (snippet requires PSv3+):
$prev = [cultureinfo]::CurrentCulture
# Temporarily switch to culture with "," as the decimal mark
[cultureinfo]::CurrentCulture = 'fr-FR'
# string expansion: culture-invariant: decimal mark is always "."
$v=1.2; "$v"; # -> '1.2'
# -f operator: culture-sensitive: decimal mark is now ","
'{0}' -f $v # -> '1,2'
[cultureinfo]::CurrentCulture = $prev
PowerShell's string expansion supports expanding collections (arrays) - it expands them to a space-separated list - whereas the -f operator only supports scalars (single values):
$arr = 'one', 'two'
# string expansion: array is converted to space-separated list
"$var" # -> 'one two'
# -f operator: array elements are syntactically treated as separate values
# so only the *first* element replaces {0}
'{0}' -f $var # -> 'one'
# If you use a *nested* array to force treatment as a single array-argument,
# you get a meaningless representation (.ToString() called on the array)
'{0}' -f (, $var) # -> 'System.Object[]'
[1] Surfacing the functionality of the $ExecutionContext.InvokeCommand.ExpandString() method in a more discoverable way, namely via an Expand-String cmdlet, is the subject of GitHub feature-request issue #11693.
Your issue occurs because the $Name string replacement is happening outside of the function, before the $Name variable is populated inside of the function.
You could do something like this instead:
function HelloWorld
{
Param ($Greeting, $Name)
$Greeting -replace '\$Name',$Name
}
HelloWorld -Greeting 'Hello, $Name!' -Name 'World'
By using single quotes, we send the literal greeting of Hello, $Name in and then do the replacement of this string inside the function using -Replace (we have to put a \ before the $ in the string we're replace because $ is a regex special character).

Powershell determine new URL of a permanently moved (redirected) resource

I'm using Powershell Core v6-beta.5 using AppImage on Linux. Is there a way to find out the "new" location of a 301 redirect?
Invoke-WebRequest -Method HEAD http://SomethingThatThrows301.com/ -MaximumRedirection 0 throws an error (Response status code does not indicate success: 301 (Moved Permanently)).
While the error does mention that the move is a 301, I'd still like a proper object telling me that, and the new address.
Is there a way to do so?
Note: All code below works in both Windows PowerShell and PowerShell Core, on all supported platforms, with up to 50 redirections by default.
Assuming:
that you don't care about the specific 3xx redirection status code and
that you only need to know the ultimate target URL (there could be a chain of redirections)
use the following:
[System.Net.HttpWebRequest]::Create('http://cnn.com').GetResponse().ResponseUri.AbsoluteUri
This yields (note how the target URL has www.):
http://www.cnn.com
Below is the source code for advanced convenience function Get-UrlRedirection, which packages the functionality, offering both resolution to the ultimate target URL and an enumeration of the chain of redirection URLs.
Example calls:
> Get-UrlRedirection http://cnn.com
http://www.cnn.com
> Get-UrlRedirection -Enumerate http://microsoft.com/about
http://microsoft.com/about
https://microsoft.com/about
https://www.microsoft.com/about
https://www.microsoft.com/about/
https://www.microsoft.com/about/default.aspx
https://www.microsoft.com/en-us/about/
Function Get-UrlRedirection {
[CmdletBinding()]
Param (
[Parameter(Mandatory, ValueFromPipeline)] [Uri] $Url,
[switch] $Enumerate,
[int] $MaxRedirections = 50 # Use same default as [System.Net.HttpWebRequest]
)
process {
try {
if ($Enumerate) { # Enumerate the whole redirection chain, from input URL to ultimate target,
# assuming the max. count of redirects is not exceeded.
# We must walk the chain of redirections one by one.
# If we disallow redirections, .GetResponse() fails and we must examine
# the exception's .Response object to get the redirect target.
$nextUrl = $Url
$urls = #( $nextUrl.AbsoluteUri ) # Start with the input Uri
$ultimateFound = $false
# Note: We add an extra loop iteration so we can determine whether
# the ultimate target URL was reached or not.
foreach($i in 1..$($MaxRedirections+1)) {
Write-Verbose "Examining: $nextUrl"
$request = [System.Net.HttpWebRequest]::Create($nextUrl)
$request.AllowAutoRedirect = $False
try {
$response = $request.GetResponse()
# Note: In .NET *Core* the .GetResponse() for a redirected resource
# with .AllowAutoRedirect -eq $False throws an *exception*.
# We only get here on *Windows*, with the full .NET Framework.
# We either have the ultimate target URL, or a redirection
# whose target URL is reflected in .Headers['Location']
# !! Syntax `.Headers.Location` does NOT work.
$nextUrlStr = $response.Headers['Location']
$response.Close()
# If the ultimate target URL was reached (it was already
# recorded in the previous iteration), and if so, simply exit the loop.
if (-not $nextUrlStr) {
$ultimateFound = $true
break
}
} catch [System.Net.WebException] {
# The presence of a 'Location' header implies that the
# exception must have been triggered by a HTTP redirection
# status code (3xx).
# $_.Exception.Response.StatusCode contains the specific code
# (as an enumeration value that can be case to [int]), if needed.
# !! Syntax `.Headers.Location` does NOT work.
$nextUrlStr = try { $_.Exception.Response.Headers['Location'] } catch {}
# Not being able to get a target URL implies that an unexpected
# error ocurred: re-throw it.
if (-not $nextUrlStr) { Throw }
}
Write-Verbose "Raw target: $nextUrlStr"
if ($nextUrlStr -match '^https?:') { # absolute URL
$nextUrl = $prevUrl = [Uri] $nextUrlStr
} else { # URL without scheme and server component
$nextUrl = $prevUrl = [Uri] ($prevUrl.Scheme + '://' + $prevUrl.Authority + $nextUrlStr)
}
if ($i -le $MaxRedirections) { $urls += $nextUrl.AbsoluteUri }
}
# Output the array of URLs (chain of redirections) as a *single* object.
Write-Output -NoEnumerate $urls
if (-not $ultimateFound) { Write-Warning "Enumeration of $Url redirections ended before reaching the ultimate target." }
} else { # Resolve just to the ultimate target,
# assuming the max. count of redirects is not exceeded.
# Note that .AllowAutoRedirect defaults to $True.
# This will fail, if there are more redirections than the specified
# or default maximum.
$request = [System.Net.HttpWebRequest]::Create($Url)
if ($PSBoundParameters.ContainsKey('MaxRedirections')) {
$request.MaximumAutomaticRedirections = $MaxRedirections
}
$response = $request.GetResponse()
# Output the ultimate target URL.
# If no redirection was involved, this is the same as the input URL.
$response.ResponseUri.AbsoluteUri
$response.Close()
}
} catch {
Write-Error $_ # Report the exception as a non-terminating error.
}
} # process
}
In order to focus on the code, I've omitted the comment-based help above; here it is - simply paste it directly above the function definition:
<#
.SYNOPSIS
Gets a URL's redirection target(s).
.DESCRIPTION
Given a URL, determines its redirection target(s), as indicated by responses
with 3xx HTTP status codes.
If the URL is not redirected, it is output as-is.
By default, the ultimate target URL is determined (if there's a chain of
redirections), but the number of redirections that are followed is limited
to 50 by default, which you may change with -MaxRedirections.
-Enumerate enumerates the redirection chain and returns an array of URLs.
.PARAMETER Url
The URL whose redirection target to determine.
You may supply multiple URLs via the pipeline.
.PARAMETER MaxRedirections
Limits the number of redirections that are followed, 50 by default.
If the limit is exceeded, a non-terminating error is reported.
.PARAMETER Enumerate
Enumerates the chain of redirections, if applicable, starting with
the input URL itself, and outputs it as an array.
If the number of actual redirections doesn't exceed the specified or default
-MaxRedirections value, the entire chain up to the ultimate target URL is
enumerated.
Otherwise, a warning is issued to indicate that the ultimate target URL wasn't
reached.
All URLs are output in absolute form, even if the targets are defined as
relative URLs.
Note that, in order to support multiple input URLs via the pipeline, each
array representing a redirection chain is output as a *single* object, so
with multiple input URLs you'll get an array of arrays as output.
.EXAMPLE
> Get-UrlRedirection http://cnn.com
http://www.cnn.com
.EXAMPLE
> Get-UrlRedirection -Enumerate http://microsoft.com/about
http://microsoft.com/about
https://microsoft.com/about
https://www.microsoft.com/about
https://www.microsoft.com/about/
https://www.microsoft.com/about/default.aspx
https://www.microsoft.com/en-us/about/
.NOTES
This function uses the [System.Net.HttpWebRequest] .NET class and was
inspired by http://www.powershellmagazine.com/2013/01/29/pstip-retrieve-a-redirected-url/
#>
If you ignore the error being thrown, you will be able to examine the HTTP response. The new URL will be in the Location header.
Try something like the below.
$url="https://jigsaw.w3.org/HTTP/300/301.html"
$resp = Invoke-WebRequest -Method HEAD $url -MaximumRedirection 0 -ErrorAction Ignore
$code = $resp.StatusCode
Write-Output "URL: $url"
Write-Output "ErrorCode: $code"
if($code -eq 301) {
$loc = $resp.Headers.Location
Write-Output "New URL: $loc"
}

Sed : Add a line at the starting of each TCL proc

I have a TCL proc like this, & want to add a line after the start of the proc, the puts "puts " entered myproc" " line
proc myproc { {filename "input.txt"}
{var1 "x"}
{var2 "y"}
{var3 "z"}
{var4 ""}
{var5 "0"}
{var6 "0"}
{var7 0}
} {
puts " entered myproc"
Can you help?
& it should also work for
proc myproc2 { N val } {
puts " entered myproc"
# comment line
set ret {} for { set i 0 } { $i < $N } { incr i } { lappend ret $val }
return $ret
}
If all you want to do is get an execution trace of your code, such as a call stack dump etc, then you don't need to modify your source code at all. You can use tcl itself to do it for you.
Tcl has no reserved keywords, none at all. Not even proc is reserved. You can therefore redefine it:
rename proc _proc
# Now proc no longer exists but we have _proc instead.
# Use it to redefine "proc":
_proc proc {name arguments body} {
set body "puts \"entered $name\";$body"
_proc $name $arguments $body
}
Just do that before running any of your own code and you'll find that every proc prints out when it's being entered on each call.
This is how a lot of tcl debuggers and profilers work - using tcl to redifine itself.
From your comments it looks like you're trying to also print how deep the stack is with each call. To do that you need to add more code to each proc definition. The most straightforward way is of course something like this:
_proc proc {name arguments body} {
set preamble"set dist2top \[info level\];puts \"\$dist2top entered $name\""
set body "$preamble;$body"
_proc $name $arguments $body
}
But as you can see, writing code inside strings can quickly become unmanagable. There are several tricks you can use to make it more manageable. One of the more common is to split $body by line and use list commands to manipulate code. It should reduce at least one level of quoting hell. My favorite is to use a templating technique similar to how you'd write html templates in MVC frameworks. I usually use string map for this:
_proc proc {name arguments body} {
_proc $name $arguments [string map [list %NAME% $name %BODY% $body] {
set dist2top [info level]
puts "$dist2top entered: %NAME%"
%BODY%
}]
}
The last argument in the _proc definition is just a string but it looks like a code block which makes it easier to read. No nasty quoting hell with this technique.
Using awk you can do:
awk '/^ *proc/ {$0 = $0 "\nputs \" entered myproc\""} 1' RS= proc-file.tcl
Gives this file:
proc myproc { {filename "input.txt"}
{var1 "x"}
{var2 "y"}
{var3 "z"}
{var4 ""}
{var5 "0"}
{var6 "0"}
{var7 0}
} {
puts " entered myproc"