Remove random items from `Get-ChildItem` output - string

I'd like to ask a question about removing lines from a variable that has strings obtained from get-childitem.
Right now, I'm extracing lines indicating files with this :
$FolderToOpen= Get-ChildItem -Path $SrcDir -Filter *.$ExtLst
That gets the files from a specified folder and specified extensions
Then, I get 4 random lines from that variable
$FourFiles = $FolderToOpen | Get-Random -Count 4
But then, here's my problem, I'd like to delete those 4 lines from the first variable. However I can't get to do that, doing
$FolderToOpen -replace "$FourFiles",""
for exemple doesn't do anything.
Could someone help me please?
Thank you in advance :)

The simplest way of doing that is to take advantage of Compare-Object which compares arrays element-wise:
Compare-Object -ReferenceObject $FolderToOpen -DifferenceObject $FourFiles -PassThru

The Get-ChildItem cmdlet returns an array of objects by default and as such is of a fixed size (see Everything you wanted to know about arrays for further information on this)
If we cast the results of the Get-ChildItem call to a list first, we'll then be able to add and remove items (without the need to create new arrays to hold the updated data)
Updated code below should give you what you're looking for:
# Call Get-ChildItem but store the results as a list of objects
# rather than a standard array of objects
[Collections.Generic.List[Object]]$FolderToOpen= Get-ChildItem -Path $SrcDir -Filter *.$ExtLst
# Get your random selection of 4 files
$FourFiles = $FolderToOpen | Get-Random -Count 4
# Remove them from the list
$FolderToOpen.RemoveAll({ param($folder) $folder -in $FourFiles })
The RemoveAll function above will return the total number of entries removed from the list (in this example, 4).

The problem with your approach is that PowerShell prefers operating on objects instead of strings. The output of Get-ChildItem is a stream of objects, what can be proven by piping it to Get-Member:
PS> dir | Get-Member
TypeName: System.IO.DirectoryInfo
...
TypeName: System.IO.FileInfo
...
Since you are interested in particularly filtering strings, you can cast all the objects to strings beforehand and then perform filtering:
$FourFiles = $FolderToOpen | Get-Random -Count 4 | % {$_.ToString()}
$FolderToOpen | Where {$_.ToString() -inotin $FourFiles}
Note that $FolderToOpen is a stream of strings, instead of one big string like you would have in Bash. Therefore I used Where to filter the entries.

A solution that uses only Get-Random. Basically use Get-Random to select the files to include, instead of exclude. Then you don't need to remove anything. The output of Get-Random is already what you want.
$FolderToOpen = #(Get-ChildItem -Path $SrcDir -Filter *.$ExtLst)
$includeCount = $FolderToOpen.Count - 4
$FolderToOpen = if( $includeCount -gt 0 ) {
$FolderToOpen | Get-Random -Count $includeCount
}
To make sure we always get an array out of Get-ChildItem, use the array sub-expression operator #(). Otherwise you'd get a single object, if Get-ChildItem outputs only a single file.
The if statement is required to make sure we don't pass an invalid -Count argument to Get-Random, when Get-ChildItems returns four or less files.
Note that this outputs the files in random order, but you could easily sort them by path again:
$FolderToOpen | Get-Random -Count $includeCount | Sort-Object FullName

Related

Replacing specific strings in all matching files content with the file's basename using PowerShell

Get-ChildItem 'C:\Users\Zac\Downloads\script test\script test\*.txt' -Recurse | ForEach {(Get-Content $_ | ForEach { $_ -replace '1000', $fileNameOnly}) | Set-Content $_ }
I have been trying to use a simple PowerShell script to replace the 1000 value in my documents with the goal of replacing the value with the name of the .nc1/.txt file it is editing.
For example a file that is called BM3333.nc1 has a line value of 1000 which needs to replace it with BM3333 so on, so forth. This will be used in batch editing.
What is the variable that I use for replacing the 1000 with the file name?
So far, I can get this to run but it doesn't replace the 1000 value, it removes it.
Your problem is that inside the ScriptBlock of a ForEach-Object invocation, the variable is $_ (also known as $PSItem). There is no name for the inner script to get the value from the outer script.
You need to create a unique name in the outer script beforehand. The ScriptBlock argument to ForEach-Object does not need to be a single expression. You can either use multiple lines or a ;.
1..3 | ForEach-Object { $a = $_; 100..105 | ForEach-Object { $_ * $a } }
For your use case, you need this variable to be the name of the file. The values in the outer ScriptBlock are System.IO.FileSystemInfo, which were returned by Get-ChildInfo.
PowerShell makes iterating on work like this very easy; try seeing which properties are available:
Get-ChildItem 'C:\Users\Zac\Downloads\script test\script test\*.txt' -Recurse | Select-Object -First 1 | Format-List *

Complicated Transposed Table With Powershell or Excel

I'm trying to manipulate the layout of a file-share report. Basically what the layout looks like now is this:
Path,Username/Group
path1,user1
path2,user1
path3,user1
path1,user2
path3,group1
path2,group2
It's showing folder paths and what users have access to them.
I'd like to change this to the following layout:
user1,user2,group1,group2
path1,path1,path3,path2
path2
path3
Whether it be importing the data into excel and manipulating it in excel or using powershell script to manipulate the data, I'm not quite sure what to do to get it the way I want.
I've tried importing this text file into excel and trying to transpose but I can't figure out how to show a list of file paths for each user. I've messed around in Access with it as well, but I'm not experienced enough in access to get it to display properly. I tried a few things in powershell but it amounted to a bunch of text documents named after the users with a list of file paths in each document. Not quite as neat as I'd like it unfortunately.
PowerShell could do it. Assuming the data is what you show in the question it looks like a CSV file. You could do:
$DataIn = Import-CSV $file
$HTOut = #{}
$File | Group 'Username/Group' | ForEach{$HTOut.add($_.Name,$_.Group.Path)}
New-Object PSObject -Prop $HTOut | Export-CSV $file
I thought about it, and this doesn't do exactly what I had said, it would make one object with a property for each user/group, and that property's value would be all of the paths for that person/group. What you really want is X objects that iterate through all of those paths. For that the first 3 lines stay the same, except that we capture the number of paths for the user/group with the most paths. Then we make that many objects iterating through paths for each user.
$DataIn = Import-CSV $file
$HTOut = #{}
$MaxPaths = $DataIn | Group 'Username/Group' | ForEach{$HTOut.add($_.Name,$_.Group.Path);$_} |% Count |Sort -Descend |Select -first 1
$Results = For($i=0;$i -le $MaxPaths;$i++){
$Record = New-Object PSObject
$HTOut.Keys|ForEach{Add-Member -InputObject $Record -NotePropertyName $_ -NotePropertyValue $(([array]$HTOut["$_"])[$i])}
$Record
}

Move files that contain a string to a subfolder with the same name as the original (PowerShell)

I'm using PowerShell and it is two days that I'm struggling on this issue.
In the directory C:\dir_1 I have many subfolders (sub_1, sub_2, ..., sub_n). Each of them contains several text files. For each subfolder i=1,2,...,n, I want to move the text files that contain the string "My-String" to the directory C:\dir_2\sub_i.
For example, if the file X in the path C:\dir1\sub_5 contains the string "My-String", I want to move it to the location C:\dir_2\sub_5. The destination folder is already existing.
I tried several modifications of the following code, but it does not work:
Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True} | Foreach-Object {Get-ChildItem "C:\dir_1\$_" | Select-String -pattern "My-String" | group path | select name | %{Move-Item $_.name "C:\dir_2\$_"}}
So, basically, what I tried to do is: foreach subfolder in dir_1, take the files that contain the string and move them to the subfolder in dir_2 with the same name. I tried several small modifications of that code, but I cannot get around my mistakes. The main error is "move-item: The given path format is not supported"... any help?
I feel like I could do better but this is my first approach
$dir1 = "C:\temp\data\folder1"
$dir2 = "C:\temp\data\folder2"
$results = Get-ChildItem $dir1 -recurse | Select-String -Pattern "asdf"
$results | ForEach-Object{
$parentFolder = ($_.Path -split "\\")[-2]
Move-Item -Path $_.Path -Destination ([io.path]::combine($dir2,$parentFolder))
}
Select-String can take file paths for its pipeline input. We feed it all the files that are under $dir1 using -recurse to get all of its children in sub folders. $results would contain an array of match objects. One of the properties is the path of the matched file.
With all of those $results we then go though each and extract the parent folder from the path. Then combine that folder with the path $dir2 in order to move it to it destination.
There are several assumptions that we are taking here. Some we could account for if need be. I will mention the one I know could be an issue first.
Your folders should not have any other subfolders under "sub_1, sub_2, ..., sub_n" else they will attempt to move incorrectly. This can be addressed with a little more string manipulation. In an attempt to make the code terse using -Recurse created this caveat.
Here is a one liner that does what you want too:
Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True} | ForEach-Object {$SubDirName = $_.Name;ForEach ($File in $(Get-ChildItem $_.FullName)){If ($File.Name -like "*My-String*"){Move-Item $File.FullName "C:\dir_2\$SubDirName"}}}
And if you'd like to see it broken out like Matt's answer:
$ParentDir = Get-ChildItem "C:\dir_1" | Where-Object {$_.PSIsContainer -eq $True}
ForEach ($SubDir in $ParentDir){
$SubDirName = $SubDir.Name
ForEach ($File in $(Get-ChildItem $SubDir.FullName)){
If ($File.Name -like "*My-String*"){
Move-Item $File.FullName "C:\dir_2\$SubDirName"
}
}
}

Removing text in a string between two characters using Powershell

I have a powershell script that runs and collects information and puts it in a .csv file. A sample of the information looks like what is listed below, with each line starting with a unique server name followed by a random unique identifier in contained a pair of ( ).
"GDR01W01SQ004 (e785754f-eeb1)","1","4","63","NY-TER-PLN-P-5N"
"GDR01L02D001 (4b889a4d-d281)","4","12","129","CO-FDP-STE-NP-5N"
I have a second powershell script that runs and takes this .csv file and its information and formats it into a report with a header and proper spacing.
Can someone please assist me with removing the text in between the ( ) as well as the ( )?
I would like the entries for each line to look like the following:
"GDR01W01SQ004","1","4","63","NY-TER-PLN-P-5N"
Thank you very much in advance!
Here is the script I have been using.
####################PowerCLI Check####################
# Verify whether the PowerCLI module is loaded, if not load it.
if ( (Get-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) -eq $null )
{
Add-PsSnapin VMware.VimAutomation.Core -ErrorAction Stop
}
################### Run Time Section ####################
#This script should be run from a server that has DNS records for all entries in vcenters.txt
$file = get-Content c:\reports\vcenter\vcenters.txt
foreach ( $server in $file) {
Connect-VIserver -Server $server
Get-VM | select Name, NumCpu, MemoryGB, ProvisionedSpaceGB, Notes | Out-Null
}
# Command for Custom Annotations.
Get-VM | select Name, NumCpu, MemoryGB, ProvisionedSpaceGB, Notes -expandproperty customfields | Export-Csv -path “c:\reports\vcenter\vcenall.csv” -NoTypeInformation
# Takes vcenall.csv and sorts only the Name and Notes columns and selects all but the custom fields. Capacity Reporting script caprep.ps1 runs against this csv.
Import-csv c:\reports\vcenter\vcenall.csv | Sort-Object Notes, Name | Select-Object Name, NumCpu, MemoryGB, ProvisionedSpaceGB, Notes |Export-csv capacity.csv -NoTypeInformation
#Used to remove domain from server name
(Get-Content capacity.csv) | ForEach-Object { $_ -replace ".domain.local", "" } | Set-Content capacity.csv
# Takes vcenall.csv and sorts only the Notes column and selects only the Name and Notes columns. Nimsoft comparison script nimcomp.ps1 runs against this csv.
Import-csv c:\reports\vcenter\vcenall.csv | Sort-Object Notes | Select-Object Name, Notes | Export-csv nimsoft.csv -NoTypeInformation
# Takes vcenall.csv and sorts only the Name columns and exports all fields. Backup/Restore comparison script bure.ps1 runs against this csv.
Import-csv c:\reports\vcenter\vcenall.csv | Sort-Object Name | Export-csv bure.csv -NoTypeInformation
I think you need to add more information but just using what you have let try this one approach
Import-Csv C:\temp\test.csv -Header server,1,2,3,4 | ForEach-Object{
$_.server = (($_.server).split("(")[0]).Trim()
$_
}
We import the csv data and assign a header. If you already have one then this parameter can be omitted.
Then we examine each row of data as an object. Change the server data by splitting it up by its spaces. If this data is for server names then it is safe to assume that that everything before the first space is the server name. This approach is dependent on the space being there. We could also use the same logic with the ( but this would be easier if the space was a guarantee.
So we update the server and then send the data back down the pipe with $_.
Sample Output
server 1 2 3 4
------ - - - -
GDR01W01SQ004 1 4 63 NY-TER-PLN-P-5N
GDR01L02D001 4 12 129 CO-FDP-STE-NP-5N
Edit based on comments
Since it is a server display name I changed the logic to split based on the "(". Also using the Split() method instead of the -split operator.

Outputting PowerShell data to a string

This is really PowerShell 101, I realise, but I'm stuck.
I'm trying to iterate through a folder tree, getting each subfolder name and a count of files. No problems there.
The new requirement is to get the ACLs on each subfolder as well. All of this data needs to be output as a CSV file, with a line consisting of each folder name, the file count, and the ACLs in a single string in one field of the CSV (I was going to delimit them with semicolons).
I am open to exporting to XML if the data can be viewed in Excel.
The part where I'm stuck is getting the ACL information into a single string for the CSV.
Get-ACL on each directory shows the data as follows (I'm doing a Select to just get the IdentityReference and FileSystemRights, which is all we're interested in):
IdentityReference FileSystemRights
----------------- ----------------
BUILTIN\Users ReadAndExecute, Synchronize
BUILTIN\Users AppendData
BUILTIN\Users CreateFiles
I would like the output file formatted with one line per subdirectory, similar to
#filecount,folder,perms
51,C:\temp,BUILTIN\Users:ReadAndExecute,Synchronize;BUILTIN\Users:AppendData...
I however can't get any kind of join working to have it presented in this way. I don't care about what combination of delimiters are used (again, must be readable in Excel).
The script, such as it is, is as follows. The output file has its line of data appended with each directory it traverses. I'm sure this isn't very efficient, but I don't want the process consuming all the server memory either. The bits I can't figure out are prepended with ###.
(Get-ChildItem C:\temp -recurse | Where-Object {$_.PSIsContainer -eq $True}) | foreach {
$a = ($_.GetFiles().Count)
$f = $_.FullName
$p = (get-acl $_.FullName).Access | select-object identityreference,filesystemrights
### do something with $p?
Out-File -FilePath c:\outfile.csv -Append -InputObject $a`,$f`,###$p?
}
Since you want all ACEs of a folder mangled into a single line you need something like this:
Get-ChildItem 'C:\temp' -Recurse | ? { $_.PSIsContainer } | % {
# build a list of "trustee:permissions" pairs
$perms = (Get-Acl $_.FullName).Access | % {
"{0}:{1}" -f $_.IdentityReference, $_.FileSystemRights
}
New-Object -Type PSObject -Property #{
'Filecount' = $_.GetFiles().Count
'Folder' = $_.FullName
'Permissions' = $perms -join ';' # join the list to a single string
}
} | Export-Csv 'c:\outfile.csv' -NoType
Repeated appending inside a loop usually guarantees poor performance, so it should be avoided whenever possible. The outer loop creates a list of custom objects, which can then be exported via Export-Csv in a single go.

Resources