Related
I am trying to understand the behavior of the #() array constructor, and I came across this very strange test.
It seems that the value of an empty pipeline is "not quite" the same as $null, even though it is -eq $null
The output of each statement is shown after the ###
$y = 1,2,3,4 | ? { $_ -ge 5 }
$z = $null
if ($y -eq $null) {'y is null'} else {'y NOT null'} ### y is null
if ($z -eq $null) {'z is null'} else {'z NOT null'} ### z is null
$ay = #($y)
$az = #($z)
"ay.length = " + $ay.length ### ay.length = 0
"az.length = " + $az.length ### az.length = 1
$az[0].GetType() ### throws exception because $az[0] is null
So the $az array has length one, and $az[0] is $null.
But the real question is: how is it possible that both $y and $z are both -eq $null, and yet when I construct arrays with #(...) then one array is empty, and the other contains a single $null element?
Expanding on Frode F.'s answer, "nothing" is a mostly magical value in PowerShell - it's called [System.Management.Automation.Internal.AutomationNull]::Value. The following will work similarly:
$y = 1,2,3,4 | ? { $_ -ge 5 }
$y = [System.Management.Automation.Internal.AutomationNull]::Value
PowerShell treats the value AutomationNull.Value like $null in most places, but not everywhere. One notable example is in a pipeline:
$null | % { 'saw $null' }
[System.Management.Automation.Internal.AutomationNull]::Value | % { 'saw AutomationNull.Value' }
This will only print:
saw $null
Note that expressions are themselves pipelines even if you don't have a pipeline character, so the following are roughly equivalent:
#($y)
#($y | Write-Output)
Understanding this, it should be clear that if $y holds the value AutomationNull.Value, nothing is written to the pipeline, and hence the array is empty.
One might ask why $null is written to the pipeline. It's a reasonable question. There are some situations where scripts/cmdlets need to indicate "failed" without using exceptions - so "no result" must be different, $null is the obvious value to use for such situations.
I've never run across a scenario where one needs to know if you have "no value" or $null, but if you did, you could use something like this:
function Test-IsAutomationNull
{
param(
[Parameter(ValueFromPipeline)]
$InputObject)
begin
{
if ($PSBoundParameters.ContainsKey('InputObject'))
{
throw "Test-IsAutomationNull only works with piped input"
}
$isAutomationNull = $true
}
process
{
$isAutomationNull = $false
}
end
{
return $isAutomationNull
}
}
dir nosuchfile* | Test-IsAutomationNull
$null | Test-IsAutomationNull
The reason you're experiencing this behaviour is becuase $null is a value. It's a "nothing value", but it's still a value.
PS P:\> $y = 1,2,3,4 | ? { $_ -ge 5 }
PS P:\> Get-Variable y | fl *
#No value survived the where-test, so y was never saved as a variable, just as a "reference"
Name : y
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
PS P:\> $z = $null
PS P:\> Get-Variable z | fl *
#Our $null variable is saved as a variable, with a $null value.
PSPath : Microsoft.PowerShell.Core\Variable::z
PSDrive : Variable
PSProvider : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name : z
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
The way #() works, is that it guarantees that the result is delievered inside a wrapper(an array). This means that as long as you have one or more objects, it will wrap it inside an array(if it's not already in an array like multiple objects would be).
$y is nothing, it's a reference, but no variable data was stored. So there is nothing to create an array with. $z however, IS a stored variable, with nothing(null-object) as the value. Since this object exists, the array constructor can create an array with that one item.
I try to make, the line from the first array is read from a file and is replaced with a line from the second array, so some times with different lines. I made a script, but I do not understand why it does not work.
$OldStrings = #(
"desktopwidth:i:1440",
"desktopheight:i:900",
"winposstr:s:0,1,140,60,1596,999"
)
$NewStrings = #(
"desktopwidth:i:1734",
"desktopheight:i:990",
"winposstr:s:0,1,50,7,1800,1036"
)
$LinesArray = Get-Content -Path 'C:\temp\My Copy\Default.rdp'
$LinesCount = $LinesArray.Count
for ($i=0; $i -lt $LinesCount; $i++) {
foreach ($OldString in $OldStrings) {
foreach ($NewString in $NewStrings) {
if ($LinesArray[$i] -like $OldString) {
$LinesArray[$i] = $LinesArray[$i] -replace $OldString, $NewString
Write-Host "`nline" $i "takes on value:" $LinesArray[$i] "`n" -ForegroundColor Gray
}
}
}
}
The file is probably why it is not read at all.
After executing the script, I see only
line 2 takes on value: desktopwidth:i:1734
line 3 takes on value: desktopwidth:i:1734
line 5 takes on value: desktopwidth:i:1734
You're looking through the string arrays twice. You want to do two loops, one for each line in the file AND another for each count in the lines you're replacing. I think this should work:
$OldStrings = #(
"desktopwidth:i:1440",
"desktopheight:i:900",
"winposstr:s:0,1,140,60,1596,999"
)
$NewStrings = #(
"desktopwidth:i:1734",
"desktopheight:i:990",
"winposstr:s:0,1,50,7,1800,1036"
)
$LinesArray = Get-Content -Path 'C:\temp\My Copy\Default.rdp'
# loop through each line
for ($i=0; $i -lt $LinesArray.Count; $i++)
{
for ($j=0;$j -lt $OldStrings.Count; $j++)
{
if ($LinesArray[$i] -match $OldStrings[$j])
{
$LinesArray[$i] = $LinesArray[$i] -replace $OldStrings[$j],$NewStrings[$j]
Write-Host "`nline" $i "takes on value:" $LinesArray[$i] "`n" -ForegroundColor Gray
}
}
}
$LinesArray | Set-Content -Path 'C:\temp\My Copy\Default.rdp'
You don't need to bother checking the lines to look for matches. Since you have the replacements ready just do the replacements outright anyway. Should be faster this way as well.
$stringReplacements = #{
"desktopwidth:i:1440" = "desktopwidth:i:1734"
"desktopheight:i:900" = "desktopheight:i:990"
"winposstr:s:0,1,140,60,1596,999" = "winposstr:s:0,1,50,7,1800,1036"
}
$path = 'C:\temp\My Copy\Default.rdp'
# Read the file in as a single string.
$fileContent = Get-Content $path | Out-String
# Iterate over each key value pair
$stringReplacements.Keys | ForEach-Object{
# Attempt the replacement for each key/pair search/replace pair
$fileContent =$fileContent.Replace($_,$stringReplacements[$_])
}
# Write changes back to file.
# $fileContent | Set-Content $path
$stringReplacements is a key value hash of search and replace strings. I don't see you writing the changes back to file so I left a line on the end for you to uncomment.
You could add in checks to do the replacements still if you value the write-host lines but I figured that was for debugging and you already know how to do that.
i have a txt file with this :
1230;
012;
45;
125
and i want to convert this in an int
but is doesn't work... he juste return the last number
here is my code :
$numbertxt = get-content -Path C:\mysticpath\number.txt -Raw
$numbertxt.GetType()
write-host $numbertxt
foreach ($flags in $numbertxt)
{
$integer = [int]$flags
}
echo $integer
somebody can help me ?
Sorry for my english
$numbertxt = (get-content -Path C:\mysticpath\number.txt -Raw) -split ';'
$numbertxt.GetType()
write-host $numbertxt
foreach ($flags in $numbertxt)
{
$integer = [int]$flags
echo $integer
}
First a integer can only be made of numbers so you will need to split the contents by ';'. This will make a array of strings that are numbers.
Also put the echo on the inside of the for loop will allow for it to display each number as its processed
try this method (control if it's convertible to integer before print)
$res=0;
#verbose version
(Get-Content "c:\temp\test.txt") -split ';' | where {[int]::TryParse($_, [ref] $res)} | foreach {$res}
#short version
(gc "c:\temp\test.txt") -split ';' | ?{[int]::TryParse($_, [ref] $res)} | %{$res}
I have a CSV file like:
"localpath"
"C:\Users\calabresel"
"C:\Users\goslinep"
"C:\Users\deangelisr"
"C:\Users\bannont"
"C:\Users\goodwind"
I am looking for a way to isolate just the username from each field. I will then query the AD to determine if each user is disabled or enabled. I haven't been able to figure out how to get just the last piece though. My idea was to use -replace to replace the identical string with null like this:
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace "C:\Users\", ""}
That came back with invalid regular expression pattern errors though which I assumed was a result of the target string containing special characters (the backslashes). I then started looking for a way to get powershell to take the \ literally instead. That lead me to try this:
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace [Regex]::Escape("C:\\Users\\"), ""}
and this
$txt = import-csv paths1.csv | % {$_.localpath = $_.localpath -replace "C:\\Users\\", ""}
both of those methods stop the invalid regular expression errors and just return me a fresh line without complaining. however when I print the $txt variable it is empty...
I'm certain I am approaching this problem from the wrong angle and/or with improper syntax but I could use some guidance as I just started working with powershell a week ago.
any help provided would be greatly appreciated.
The following will import the CSV file and then get the leaf of the path. I.e the user name.
$txt = Import-Csv paths1.csv | ForEach-Object { Split-Path $_.localpath -leaf }
If you still want to use your replace method, just take out the $_.localpath = part and it should work.
$txt = Import-Csv C:\##Scatch\test.csv | % { $_.localpath -replace "C:\\Users\\", ""}
The reason why you aren't getting anything back into $txt is that you update a property of $_ but don't return $_.
Assuming that you want to use the regex rather than Split-Path
$txt = import-csv C:\temp\test.csv | % {
$_.localpath = $_.localpath -replace "C:\\Users\\", ""
$_
}
Or
$txt = import-csv C:\temp\test.csv | % {
$_.localpath -replace "C:\\Users\\", ""
}
other solution
Get-Content "C:\temp\test.txt" | select #{N="Value";E={$_.split('\')[-1].replace('"', '')}} -Skip 1
I would like to find all directories at the top level from the location of the script that are stored in subversion.
In C# it would be something like this
Directory.GetDirectories(".")
.Where(d=>Directories.GetDirectories(d)
.Any(x => x == "_svn" || ".svn"));
I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.
So far I've got this:
Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"
This finds me the svn directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding
| Select-Object {$_.Directory}
to the end of that command list simply displays a sequence of blank lines.
To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory limits the matches to directories, -Recurse -Depth 2 recurses up to three levels (children, grandchildren, and great-grandchildren), Include allows specifying multiple (filename-component) filters, and .Parent.FullName returns the full path of the parent dirs. of the matching dirs., using member-access enumeration (implicitly accessing a collection's elements' properties).
As for the bonus question: select-object {$_.Directory} does not work,
because the \[System.IO.DirectoryInfo\] instances returned by Get-ChildItem have no .Directory property, only a .Parent property; Select-Object -ExpandProperty Parent should have been used.
In addition to only returning the property value of interest, -ExpandProperty also enforces the existence of the property. By contrast, Select-Object {$_.Directory} returns a custom object with a property literally named $_.Directory, whose value is $null, given that the input objects have no .Directory property; these $null values print as empty lines in the console.
As for the more general question about a PowerShell equivalent to LINQ's .Any() method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
Using the PowerShell v4+ intrinsic.Where() method:
Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.
(...).Where({ $_ ... }, 'First').Count -gt 0
... represents the command of interest, and $_ ... the condition of interest, applied to each input object, where PowerShell's automatic $_ variable refers to the input object at hand; argument 'First' ensures that the method returns once the first match has been found.
For example:
# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True
Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:
The advantage of a pipeline-based solution is that it can act on a command's output one by one, as it is being produced, without needing to collect the entire output in memory first.
If you don't mind that all objects are enumerated - even if you only care if there is at least one - use Paolo Tedesco's helpful extension to JaredPar's helpful answer.
The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though - logically - the determination whether there are any output objects can be made as soon as the first object is received.
If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:
[Ad-hoc: Easy to understand, but cumbersome to implement]
Enclose the pipeline in a dummy loop and use break to break out of the pipeline and that loop (... represents the command whose output to test, and $_ ... match the condition):
# Exit on first input object.
[bool] $haveAny = do { ... | % { $true; break } } while ($false)
# Exit on first input object that matches a condition.
[bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of function Test-Any below.
It can be added to scripts or, for use in interactive sessions, to your $PROFILE file.
PowerShell v3+: Optimized utility function Test-Any
The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
#requires -version 3
Function Test-Any {
[CmdletBinding()]
param(
[ScriptBlock] $Filter,
[Parameter(ValueFromPipeline = $true)] $InputObject
)
process {
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
$true # Signal that at least 1 [matching] object was found
# Now that we have our result, stop the upstream commands in the
# pipeline so that they don't create more, no-longer-needed input.
(Add-Type -Passthru -TypeDefinition '
using System.Management.Automation;
namespace net.same2u.PowerShell {
public static class CustomPipelineStopper {
public static void Stop(Cmdlet cmdlet) {
throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
}
}
}')::Stop($PSCmdlet)
}
}
end { $false }
}
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) defaults to true if $Filter wasn't specified, and otherwise evaluates the filter (script block) with the object at hand.
The use of ForEach-Object to evaluate the filter script block ensures that $_ binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl's helpful answer here.
The (Add-Type ... statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception that Select-Object -First (PowerShell v3+) uses internally to stop the pipeline, namely [System.Management.Automation.StopUpstreamCommandsException], which as of PowerShell v5 is still a private type.
Background here:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
A big thank-you to PetSerAl for contributing this code in the comments.
Examples:
PS> #() | Test-Any false
PS> Get-EventLog Application | Test-Any # should return *right away* true
PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true
Background information
JaredPar's helpful answer and Paolo Tedesco's helpful extension fall short in one respect: they don't exit the pipeline once a match has been found, which can be an important optimization.
Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
A naïve optimization of JaredPar's answer actually shortens the code:
# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
The process block is only entered if there's at least one element in the pipeline.
Small caveat: By design, if there's no pipeline at all, the process block is still entered, with $_ set to $null, so calling Test-Any outside of a pipeline unhelpfully returns $true. To distinguish between between $null | Test-Any and Test-Any, check $MyInvocation.ExpectingInput, which is $true only in a pipeline: Thanks, PetSerAl
function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
$true, written to the output stream, signals that at least one object was found.
break then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP - break is NOT designed to exit a PIPELINEThanks, PetSerAl
.
If there were a command to exit the pipeline, this is where it would go.
Note that return would simply move on to the next input object.
Since the process block unconditionally executes break, the end block is only reached if the process block was never entered, which implies an empty pipeline, so $false is written to the output stream to signal that.
Unfortunately there is no equivalent in PowerShell. I wrote a blog post about this with a suggestion for a general purpose Test-Any function / filter.
function Test-Any() {
begin {
$any = $false
}
process {
$any = $true
}
end {
$any
}
}
Blog post: Is there anything in that pipeline?
A variation on #JaredPar's answer, to incorporate the test in the Test-Any filter:
function Test-Any {
[CmdletBinding()]
param($EvaluateCondition,
[Parameter(ValueFromPipeline = $true)] $ObjectToTest)
begin {
$any = $false
}
process {
if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
$any = $true
}
}
end {
$any
}
}
Now I can write "any" tests like
> 1..4 | Test-Any { $_ -gt 3 }
True
> 1..4 | Test-Any { $_ -gt 5 }
False
You can use the original LINQ Any:
[Linq.Enumerable]::Any($list)
It's actually quite simple - just select first $true (formatted for clarity):
[bool] ($source `
| foreach { [bool] (<predicate>) } `
| where { $_ } `
| select -first 1)
Alternative way:
($source `
| where { <predicate> } `
| foreach { $true } `
| select -first 1)
My approach now was:
gci -r -force `
| ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
| select Parent -Unique
The reason why
select-object {$_.Directory}
doesn't return anything useful is that there is no such property on a DirectoryInfo object. At least not in my PowerShell.
To elaborate on your own answer: PowerShell can treat most non-empty collections as $true, so you can simply do:
$svnDirs = gci `
| ? {$_.PsIsContainer} `
| ? {
gci $_.Name -Force `
| ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
}
I ended up doing it with a count:
$directoryContainsSvn = {
(Get-ChildItem $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = Get-ChildItem | ? {$_.PsIsContainer} | ? $directoryContainsSvn
You can tighten this up a bit:
gci -fo | ?{$_.PSIsContainer -and `
(gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}
Note - passing $__.Name to the nested gci is unnecessary. Passing it $_ is sufficent.
I recommend the following solution:
<#
.SYNOPSIS
Tests if any object in an array matches the expression
.EXAMPLE
#( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $True)]
$Condition,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Item
)
begin {
[bool]$isMatch = $False
}
process {
if (& $Condition $Item) {
[bool]$isMatch = $true
}
}
end {
Write-Output $isMatch
}
}
# optional alias
New-Alias any Where-Any
This is the best method that I found so far (does not iterate over all elements if already found a true, and does not break the pipeline):
From LINQ Any() equivalent in PowerShell
It’s possible to use a built-in $input variable that contains the whole pipeline in a scope of function.
So, the desired code could look like the following:
function Test-Any([scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null)
{
if ($debugOut)
{
Write-Host(“{0} | % {{{1}}}” -f $input, $scriptBlock)
}
$_ret = $false;
$_input = ($input -as [Collections.IEnumerator])
if ($_input)
{
while ($_input.MoveNext())
{
$_ = $_input.Current;
Write-Host $_
if ($debugOut)
{
Write-Host(“Tested: [{0}]” -f (&$debugOut))
}
if (&$scriptBlock)
{
if ($debugOut)
{
Write-Host(“Matched: [{0}]” -f (&$debugOut))
}
$_ret = $true
break
}
}
}
$_ret
}
I think that the best answer here is the function proposed by #JaredPar, but if you like one-liners as I do I'd like to propose following Any one-liner:
# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }
%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } checks that at least one item match condition.
One note - usually the Any operation evaluates the array until it finds the first item matching the condition. But this code evaluates all items.
Just to mention, you can easily adjust it to become All one-liner:
# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }
%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } checks that all items match condition.
Notice, that to check Any you need -or and to check All you need -and.
I took a more linq-style approach.
I know this question is probably super old. I used this to accomplish my needs:
PS> $searchData = "unn"
PS> $StringData = #("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate = [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)
Taken from here:
https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/#post-71022-_Toc482783751