Powershell file URL / file filtering - html

I am trying to generate an html page with file index. This approach worked seamlessly:
$htmlout = Get-ChildItem -Path "$SearchPath" -Filter "$fileType" -Recurse |
Select #{Name="Link";Expression={("<a rel=" + $_.FullName + " href=file:///" + $_.FullName + ">$_</a>")}}
The Link column had file names only (i.e. test.txt) and displayed file content when clicking on it. Then we've got an additional requirement to skip old files. The script is now:
$htmlout = Get-ChildItem -Path "$SearchPath" -Recurse -include ("$fileType") | Where-Object {$_.LastWriteTime -ge "01/01/2014"} |` Select #{Name="Link";Expression={("<a rel=" + $_.FullName + " href=file:///" + $_.FullName + ">$_</a>")}}
It still works, but Link column now displays the entire file path + file name (i.e. \fileserver\folder1\folder2\test.txt).
Adding >$_.Name< does not work here.
I am trying to understand why the same URL line behaves differently after filter change.

Background
As far as I can tell, there is a discrepancy with the interaction between the .ToString() method and the DefaultDisplayProperty of objects returned by Get-ChildItem.
The behavior manifests when both of the following conditions are true:
- The -filter parameter is being used.
- The value of the -Path parameter resolves to a single directory, whether or not -Recurse is used.
Under the above circumstances, the .ToString() method implemented by PowerShell uses the Name property as default, rather than FullName as is the case in all other scenarios.
My guess is that this inconsistency is due to the underlying object types returned by the FileSystem provider when -Filter is used, rather than the objects PowerShell returns when it handles the search/filter itself (as is the case with -Include).
Observation
When you wrap your $_ pipeline object variable in double quotes, PowerShell's type-conversion implicitly calls the .ToString() method and you get the resulting name variation.
Solution
To correct your issue, you could simply use -Filter in both code examples and get the desired output, however, that is prone to cause problems sooner or later.
The more appropriate way to negate the problem is to properly use a PowerShell sub-expression within the double-quoted strings.
To create a sub-expression, simply wrap the desired code like so: $(). This creates a separation between which characters are code and which are part of the string; in your case allowing you to use the member access operator .. The method also alleviates the need to do string concatenation with the + operator.
Solution Code:
$HTMLOut = Get-ChildItem -Path $SearchPath -Recurse -Include $FileType | Where-Object {$_.LastWriteTime -ge "01/01/2014"} | Select #{Name="Link";Expression={("<a rel=$($_.FullName) href=file:///$($_.FullName)>$($_.Name)</a>")}}

Related

Error trying to get data from JSON file in PowerShell [duplicate]

The txt file is just a bunch of UNC paths, i am trying to get a list of UNC paths from this text file put into another text file after the test-path is validated. it shows the validated paths on screen but the text file does not populate.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { write-host "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}
To complement Zam's helpful answer with background information:
Write-Host writes to the host[1] (typically, the console aka terminal), which bypasses PowerShell's success output stream and therefore sends nothing trough the pipeline.
See the bottom section of this answer for when Write-Host is appropriate; in short: you should generally only use it for display-only output.
Write-Output is the appropriate cmdlet for producing data output, but it is rarely necessary, because you can rely on PowerShell's convenient implicit output behavior, as shown in Zam's answer and explained in this answer.
Also, your command will perform much better if you simply pipe the % (ForEach-Object) command's output as a whole to a single Out-File call, rather than calling Out-File -Append for each input path.
Instead of using % with conditional explicit output, you can more elegantly implement your command with the Where-Object cmdlet:
Get-Content .\cfgpath.txt |
Where-Object { Test-Path $_ } |
Out-File -FilePath c:\temp\1.txt
Also note that for saving strings to a file it is more efficient to use Set-Content instead of
Out-File, though note that in Windows PowerShell the default output character encoding differs (no longer a concern in PowerShell [Core] 6+, which consistently defaults to BOM-less UTF-8); see this answer for when to choose which cmdlet.
By contrast, Out-File and > (its effective alias) use PowerShell's formatting system to write for-display representations of any non-string input objects to the output file, the same way that output renders to the display by default.
In other words: To save objects to a file in a way that is suitable for later programmatic processing, you need to use a structured file format, such as CSV (Export-Csv) or JSON (ConvertTo-Json, combined with Set-Content).
[1] In PowerShell 5.0 and higher, Write-Host now writes to a new stream, the information stream (number 6), which by default prints to the host. See about_Redirection.
Therefore, a 6> redirection now technically does allow you to send Write-Host output through the pipeline (though doing so is not a good idea) or capture / redirect it; e.g.,
Write-Host hi 6>&1 | % { "[$_]" }. Note that the type of the objects output by this redirection is System.Management.Automation.InformationRecord.
Write-Host only writes to the console. I believe what you want there is Write-Output.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { write-output "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}
Additionally you can just omit the Write-Output and that works too.
$cfgs = Get-Content .\cfgpath.txt
$cfgs | % {
if (Test-Path $_) { "$_" | Out-File -FilePath c:\temp\1.txt -Append }
}

Is there a difference in the string object created by out-string and toString()?

I'm parsing some text from a json file and based on the text I want to do things to the text.
foreach ($jsonText in $jsonFile.row[0]){
$stringA = $jsonText.ToString()
$stringB = $jsonText| Out-String
switch ($stringA)
{
'A' {'do things'}
'B' {'do other things'}
'C' {'do somethings'}
}
The string from piping out-string does not produce a string that would work in the switch case, so I am wondering if there is a difference with these two strings?
The fundamental difference is that Out-String uses PowerShell's rich for-display output-formatting system for non-string, non-primitive objects, whereas .ToString() simply delegates the stringification to the .NET type at hand (which, unless overridden by a type, simply reports the full type name).
In other words: Out-String by default creates a single, multi-line string with the same richly formatted, for-the-human-observer representation you would see in the console.
Adding -Stream sends this representation line by line through the pipeline (resulting in an array of lines if captured in a variable). Given that representations of complex objects are multi-line and in tabular form share a header, a single line does not correspond to a single input object.
As of PowerShell 7.2, an unfortunate aspect of Out-String (without -Stream) is that it appends a trailing newline to whatever the output-formatting system reports, and that also applies to strings as input, which PowerShell's formatting system otherwise represents as-is; similarly, .NET primitive types and a few single-value-only types are represented by their .ToString() return values.
GitHub issue #14444 discusses this problematic behavior.
# Unfortunately, these equivalences are true.
# With a single input object, adding -Stream would avoid the trailing newline.
('foo'.ToString() + [Environment]::NewLine) -eq ('foo' | Out-String)
((42).ToString() + [Environment]::NewLine) -eq (42 | Out-String)
Uses of Out-String
Use it if you explicitly want a string representation of the rich, for-display representation that PowerShell's formatting system produces.
Combined with -Stream, this can serve as a quick-and-dirty way to search through the display output of a command via Select-String; PowerShell even comes with function, oss, that wraps Output-String -Stream, so that you can do something like:
# Quick-and-dirty search through the formatted representations
# of all defined drives, without having to worry about property names.
Get-PSDrive | oss | Select-String \\server1
Regrettably, Select-String doesn't search through the formatted representations by default (in which case you could omit the oss call), even though that would make sense (it actually searches through the typically useless .ToString() representations).
GitHub issue #10726 proposes changing the default behavior.
With external programs, you can use it to join their output lines - which PowerShell invariably interprets as text (strings) - into a single-multiline string.
E.g, to get the output from tzutil /l as a single, multi-line string:
$output = tzutil /l | Out-String
Unfortunately, this is again hampered by the unexpected addition of a trailing newline, as discussed in GitHub issue #14444; workarounds:
Trim the trailing newline, in the simplest case with .Trim()
$output = (tzutil /l | Out-String).Trim()
Use a -join operation instead:
$output = (tzutil /l) -join [Environment]::NewLine

PowerShell IntelliSense on parameters [duplicate]

The code below is part of a switch and it's working fine, but the problem is: I need to change my file name to 15... Is it possible to to change it so that when I start it, it waits to select for a file with the tab key? Something like when you write Import-Csv in a PowerShell console and press Tab it shows all possbile paths and files.
$names = Import-Csv 15.csv -Header Givenname,Surname -Delimiter ";"
Write-Host "Rename your csv file to '15' and put it in same folder with this script" -ForegroundColor Cyan
pause
foreach ($Name in $Names) {
$FirstFilter = $Name.Givenname
$SecondFilter = $Name.Surname
Get-ADUser -Filter {GivenName -like $FirstFilter -and Surname -like $SecondFilter} |
select Enabled, SamAccountName, DistinguishedName,
#{n="ou";e={($_.DistinguishedName -split ",*..=")[2]}} |
Export-Csv .\sam.csv -NoTypeInformation -Append
}
So you want Intellisense in your script. Ambitious move. Most people would settle for the file browser dialog box. Anyway, I am going to have to refer you to smarter men than me. I was thinking ValidateSet attribute would serve your purpose but I realized that the traditional param block is not enough. So I looked up DynamicParams and this is what I found. This should work for you.
https://blogs.technet.microsoft.com/pstips/2014/06/09/dynamic-validateset-in-a-dynamic-parameter/
The simplest solution is to make your script accept the target file as an argument, by declaring a parameter:
param(
# Declare a mandatory parameter to which the file path of the CSV
# file to import must be passed as an argument on invocation.
[Parameter(Mandatory)]
[string] $FilePath
)
$names = Import-Csv $FilePath -Header Givenname,Surname -Delimiter ";"
foreach ($Name in $Names) {
$FirstFilter = $Name.Givenname
$SecondFilter = $Name.Surname
Get-ADUser -Filter {GivenName -like $FirstFilter -and Surname -like $SecondFilter} |
select Enabled, SamAccountName, DistinguishedName,
#{n="ou";e={($_.DistinguishedName -split ",*..=")[2]}} |
Export-Csv .\sam.csv -NoTypeInformation -Append
}
If you invoke your script without a file path, you will be prompted for it; let's assume your script is located in the current dir. and its name is someScript.ps1:
./someScript # invocation with no argument prompts for a value for $FilePath
Unfortunately, such an automatic prompt is not user-friendly and offers no tab completion.
However, on the command line PowerShell's tab completion defaults to completing file and directory names in the current location, so that:
./someScript <press tab here>
cycles through all files and directories in the current folder.
You can even type a wildcard expression and tab-complete that, if you don't know the full filename or don't want to type it in full:
./someScript *.csv<press tab here>
This will cycle through all *.csv files in the current dir. only.
If you want to go even further and customize tab completion to only cycle through *.csv files, you can use an [ArgumentCompleter({ ... })] attribute (PSv5+):
param(
[Parameter(Mandatory)]
# Implement custom tab-completion based on only the *.csv files in the current dir.
[ArgumentCompleter({
param($cmd, $param, $wordToComplete)
Get-ChildItem -Name "$wordToComplete*.csv"
})]
[string] $FilePath
)
# ...
Now,
./someScript <tab>
will cycle only through the *.csv files in the current directory, if any.
Caveat: As of PowerShell 7.0, tab-completing an argument for which the ArgumentCompleter script block returns no matches (in this case, with no *.csv files present) unexpectedly falls back to the default file- and directory-name completion - see this GitHub issue.
Similarly,
./someScript 1<tab>
will cycle only through the *.csv files in the current directory whose name starts with 1, if any.
As an alternative to using an attribute as part of a script's / function's definition, you can use the PSv5+ Register-ArgumentCompleter cmdlet to attach tab completions to the parameters of any command, i.e., including preexisting ones.
In PSv4- you have two (cumbersome) options for custom tab completion:
Use a dynamic parameter with a dynamically constructed [ValidateSet()] attribute - see the link in Rohin Sidharth's answer.
Customize the tabexpansion2 (PSv3, PSv4) / tabexpansion (PSv1, PSv2) function, but be sure not to accidentally replace existing functionality.
Below is my example.ps1 file that I use to write 1-off scripts. In your case I think you can get what you want with it. For example (no pun intended) you could call this script by typing
C:\PathToYourScripts\example.ps1 [tab]
where [tab] represents pressing the tab key. Powershell intellisense will kick in and offer autocompletion for file names. If your .csv file is not in the current director you can easily use Powershell intellisense to help you find it
C:\PathToYourScripts\example.ps1 C:\PathToCSvFiles[tab]
and powershell will autocomplete. Would-be-downvoters might notice that powershell autocomplete is definitely NOT a complete file-picker but this seems to fulfill the intent of the asked question. Here's the sample.
<#
.NOTES
this is an example script
.SYNOPSIS
this is an example script
.DESCRIPTION
this is an example script
.Example
this is an example script
.LINK
https://my/_git/GitDrive
#>
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact="Low")]
param (
[string] $fileName
)
Begin {
}
Process {
if ($PSCmdlet.ShouldProcess("Simulated execution to process $($fileName): Omit -Whatif to process ")) {
Write-Information -Message "Processing $fileName" -InformationAction Continue
}
}
End {
}
If you want to get autocomplete help for multiple parameters just type in the parameter name(s) and press [tab] after each one. Note that leaving the parameters blank will not break the script but you can either extend this to mark the parameters required or just fail with a helpful message. That seems a bit beyond the original question so I'll stop here.

Replace column values with Powershell

I'm trying to cycle through a csv and replace any values in a column named Enabled from True to A.
Import-Csv .\test.csv | Where-Object {$_.Enabled -eq 'True'} --> what goes here to replace 'True' with 'A'?
Where-Object acts like a filter, so the columns that get passed to the rest of the pipeline will only be the ones where Enabled is True; which will prevent you from including the others in your output file (I'm assuming you want to have a complete file at the end).
So I would recommend using ForEach-Object and then modifying based on a condition inside there, but still passing each object through (modified or not):
Import-Csv .\test.csv | ForEach-Object {
if ($_.Enabled -eq 'True') {
$_.Enabled = 'A'
}
$_
} | Export-Csv .\test-modified.csv -NoTypeInformation
Briantist's answer works just fine. If you really wanted to get crazy with it you could create an Excel comobject, select the workbook/sheet, then select the "enabled" entire column, snip out empty cells/column header, then loop through and essentially do the same thing as what briantist said, although this way you can do things like add conditional formatting, etc. Just depends what all you are trying to do

Output an array to HTML file

I have been given a working PowerShell script to modify. This script checks for broken links in a SharePoint web application and reports any broken links it finds. Currently, the script outputs the results to a text file.
Code:
$results | Out-File report.txt
Result Format:
Name of link list - ID of the item - URL + URL Name - HTTP status code
- URL
Result:
Link List 1 - 1 - http://google.com, Google (Good) - 200 -
http://google.com
However, when I try to use the ConvertTo-HTML function I get a different output:
Code:
$results | ConvertTo-Html | Out-File report.html
Result:
76
Therefore, instead of receiving a string of text I am receiving its length.
What am I doing wrong here?
Note:
results is an array.
Convertto-Html is not meant to be used like that, from help:
Converts Microsoft .NET Framework objects into HTML that can be displayed in a Web browser.
You need to send an object (resulting from a command) to this cmdlet, not just a bunch of text. See: Get-Help Convertto-Html -Examples
this is a way to do what you want:
$results | ForEach-Object {Add-Member -InputObject $_ -Type NoteProperty -Name Value -Value $_; $_} | ConvertTo-Html -Property Value