File sorting based on file content (string) - arrays

I want to build a modular script that sorts files based on content (strings/Get-Content in PowerShell).
Requirement:
Defining a directory. ($directory)
start a foreach loop: foreach
list items in the directory & full path in memory
$FilePath in Get-ChildItem $directory | Select-Object -ExpandPropert FullName
Load content of one file at a time in the memory
$content = Get-Content $FilePath
Search for the keyword and copy the file once a particular keyword is found.
if ($content -match 'keyword1') { Copy-Item $FilePath $OutputPath }
While I am able to do this in a static manner using the below mentioned code, I wanted to modularise it for reuse.
[string] $Directory = "parth to source directory";
[string] $outpath1 = "outpath for keyword1";
[string] $OutputPath2 = "outpath for keyword2";
[string] $OutputPath3 = "outpath for keyword3";
foreach ($FilePath = Get-ChildItem $Directory | Select-Object -ExpandProperty FullName) {
[string] $content = Get-Content $FilePath
if ($content -match 'keyword1') {
Copy-Item $FilePath $OutputPath
} elseif ($content -match 'keyword2') {
Copy-Item $FilePath $OutputPath2
} else {
Copy-Item $FilePath $keyword3
}
}
My questions:
Is it possible to define keywords in a single array? If so how do that in PowerShell? (keyword1, keyword2, keyword3)
Run keywords sequentially in the files and whenever one keyword is detected, the file is copied to it's designated folder. Can I have this done in modular fashion or will I have to define directory for each keyword?
The reason I am doing this is because while the script is being used for 2 or 3 keywords as of now, it will be used for over 50 keywords and allowing reuse should help.

What you describe could be achieved with a hashtable and a nested loop:
$outpath = #{
'keyword1' = 'outpath for keyword1'
'keyword2' = 'outpath for keyword2'
'keyword3' = 'outpath for keyword3'
}
foreach ($FilePath in Get-ChildItem $Directory | Select-Object -Expand FullName) {
$content = Get-Content $FilePath
foreach ($keyword in $outpath.Keys) {
if ($content -match $keyword) {
Copy-Item $FilePath $outpath[$keyword]
break
}
}
}
Alternatively you could use a switch statement:
$outpath = #{
'keyword1' = 'outpath for keyword1'
'keyword2' = 'outpath for keyword2'
'keyword3' = 'outpath for keyword3'
}
$pattern = ($outpath.Keys | ForEach-Object { [regex]::Escape($_) }) -join '|'
foreach ($FilePath in Get-ChildItem $Directory | Select-Object -Expand FullName) {
$content = Get-Content $FilePath
switch -regex ($content) {
$pattern {
Copy-Item $FilePath $outpath[$keyword]
break
}
}
}
The latter would also give you a simple way of specifying a fallback destination path if you also want to handle files with no matching keyword.
$fallbackpath = '...'
foreach ($FilePath in Get-ChildItem $Directory | Select-Object -Expand FullName) {
$content = Get-Content $FilePath
switch -regex ($content) {
$pattern {
Copy-Item $FilePath $outpath[$keyword]
break
}
default {
Copy-Item $FilePath $fallbackpath
break
}
}
}

Related

Multiple varibales from a function into an array in Powershell

I have a function that returns several things and I need to store them into an array seperately.
The code I currently have is like so:
Function ADlocation{
Try{
$ADDetails = Get-ADComputer - Identity $Servername -Properties Description,LastLogOnTimeStamp -ErrorAction SilentlyContinue -ErrorVariable ADFail
}
Catch [Exception]{
return "$($Servername) not in AD"
}
If(!ADFail){
return (Get-ADOrganizationalUnit -Identity $(ADDetails.DistinguishedName.Replace("CN=$($ADDetails.Name),","")) -Properties canonicalName).canonicalName
return $ADDetails.Description
return ([datetime]::FromFileTime($ADDetails.LastLogonTimeStamp)).ToString()
}
}
$Output = #()
foreach ($ipAddress in $iplist){
$Servername = [System.Net.Dns]::GetHostByAddress($ipAddress).Hostname
if(Test-Connection $ipAddress -Quiet){
$Output += [PSCustomObject]#{
ip = $ipAddress
Name = $ServerName
Pingable = "Yes"
ADLocation = ADlocation
AdDescription = ADlocation
LAstLogOnTime = ADlocation
}
} else {
$Output +=[PSCustomObject]#{
ip = $ipAddress
Name = "N/A"
Pingable = "No"
}
}
}
$Output | Export-Csv -path $OutputPath -NoTypeInformation
I am unsure what i should call to specifically get the "ADlocation", "ADDescription" and LastLogOnTime
There are a couple of things amiss in your code. As commented, the three return statements in the function. In fact, you don't really need a helper function for this..
Also, there is a syntax error on - Identity $Servername, where the space should not be there between the hyphen and the parameter name Identity.
Then, if you want to output a valid CSV, you need to specify the same objects with the same properties, both when succeeded and when failed.
I think the easiest way to do this, is to merge success/failed like below:
Assuming your $iplist variable is an array of IP addresses
$OutputPath = 'D:\Test\computers.csv' # enter the path and filename you want here
# loop over the IP addresses in the list
$Output = foreach ($ipAddress in $iplist) {
# initialize some variables
$pingable = 'No'
$Servername, $ADDetails = $null
if (Test-Connection -ComputerName $ipAddress -Quiet -Count 1) {
$pingable = 'Yes'
# GetHostByAddress is obsolete, use GetHostEntry
$Servername = [System.Net.Dns]::GetHostEntry($ipAddress).Hostname
# rather use Filter than Identity so exceptions can be silenced with -ErrorAction SilentlyContinue
$ADDetails = Get-ADComputer -Filter "Name -eq '$Servername'" -Properties Description,LastLogOnDate, CanonicalName -ErrorAction SilentlyContinue
}
# simply output an object to be collected in variable $Output
[PSCustomObject]#{
IP = $ipAddress
Name = if ([string]::IsNullOrWhiteSpace($ServerName)) { 'N/A' } else { $ServerName }
Pingable = $pingable
ADLocation = if ($ADDetails) { Split-Path -Path $ADDetails.CanonicalName -Parent } else { 'N/A' }
ADDescription = if ($ADDetails) { $ADDetails.Description } else { 'N/A' }
LastLogOnDate = if ($ADDetails) { $ADDetails.LastLogOnDate } else { 'N/A' }
}
}
# output on screen
$Output | Format-Table -AutoSize
# output to CSV file
$Output | Export-Csv -Path $OutputPath -NoTypeInformation

How to adapt Get-ChildItemToDepth to return Depth in array ?

I using PowerShell 2.0 and using a function to list all directory ending by "_S" on 2 level depth
Example where Push-Location = "\\MyServer\Shared\toto\" the result is:
\\MyServer\Shared\toto\Folder1_S
\\MyServer\Shared\toto\Folder1_S\Folder2_S
\\MyServer\Shared\toto\Folder1_S\Folder3_S
Now, i would like this function return level depth number in array
For example i would like this result
1; \\MyServer\Shared\toto\Folder1_S
2;\\MyServer\Shared\toto\Folder1_S\Folder2_S
2; \\MyServer\Shared\toto\Folder1_S\Folder3_S
.
function Get-ChildItemToDepth {
param(
[String]$Path = $PWD,
[String]$Filter = "*_S",
[Byte]$ToDepth = 2,
[Byte]$CurrentDepth = 0,
[Switch]$DebugMode
)
$CurrentDepth++
if ($DebugMode) { $DebugPreference = "Continue" }
Get-ChildItem $Path | ForEach-Object {$_ | Where-Object { ($_.Attributes -match "Directory") -and ($_.Name -like $Filter) } | Select-Object -ExpandProperty FullName
#Write-Host $CurrentDepth
if ($_.PsIsContainer) {
if ($CurrentDepth -le $ToDepth) {
# Callback to this function
Get-ChildItemToDepth -Path $_.FullName -Filter $Filter -ToDepth $ToDepth -CurrentDepth $CurrentDepth
} else {
Write-Host $("Skipping GCI for Folder: $($_.FullName) " +
"(Why: Current depth $CurrentDepth vs limit depth $ToDepth)")
}
}
}
}
How to adpat the funtion to return an array with depth ?
Instead of outputing the FullName-value, create a string with your current depth and fullname. Ex (I've also cleaned it up a bit):
function Get-ChildItemToDepth {
param(
[String]$Path = $PWD,
[String]$Filter = "*_S",
[int]$MaxDepth = 2,
[int]$CurrentDepth = 1,
[Switch]$DebugMode
)
if ($DebugMode) { $DebugPreference = "Continue" }
Get-ChildItem $Path | Where-Object { $_.PSIsContainer } | ForEach-Object {
#Write-Host $CurrentDepth
if ($_.Name -like $Filter) {
#Match found. Output "Level; Path"
"$CurrentDepth; $($_.FullName)"
}
#Recursion
if ($CurrentDepth -lt $MaxDepth) {
# Callback to this function
Get-ChildItemToDepth -Path $_.FullName -Filter $Filter -MaxDepth $MaxDepth -CurrentDepth ($CurrentDepth + 1)
} else {
Write-Host $("Skipping GCI for Folder: $($_.FullName) " +
"(Why: Current depth $CurrentDepth vs limit depth $MaxDepth)")
}
}
}
Get-ChildItemToDepth -Path \\MyServer\Shared\toto\

PowerShell nested loop for renaming files in directory

I am trying to do a nested loop in PowerShell. I am renaming images in a several sub directories but i need the loop to reset based on it finding a new directory.
Here is the code I wrote but I can't get it to work as it gives me an error saying the file doesn't exist.
function rename-cruise {
Param([string]$parkname, [string]$extension=".jpg")
if(!$parkname){
$park = $args[0];
} else {
$park = $parkname;
}
$dirs = Get-ChildItem -Recurse | ?{ $_.PSIsContainer };
foreach($dir in $dirs){
if($dir -cnotlike "177"){
$i = 0;
$currentDir = get-location;
$location = "$currentDir\$dir";
write-host $currentDir
write-host $location
Get-ChildItem -Path $location -Filter "*$extension" |
ForEach-Object {
if($i -lt 10){
$newName = "$park-0$i$extension";
Rename-Item $_.Name -NewName $newName;
} else {
$newName = "$park-$i$extension";
Rename-Item $_.Name -NewName $newName;
}
$i++;
}
}
}
}

Automatically import ICS file to outlook.com

I have an *.ics file and want to import it to my calendar on outlook.com. How can I do this with a powershell script?
I need to either delete and recreate the calendar I import to, or clear the calendar before import.
Thanks in advance.
Try
Step 1: Read the contents of the ics file
Step 2: Parse it
Step 3: Use Outlook Application Object in Powershell
Step 4: Get the Calendar folder
Step 5: use the properties of the calendar folder to add the parsed content in step 2
#Folder containing ICS files
$ICSpath="C:\Users\test\testasdasd"
$ICSlist = get-childitem $ICSPath
Foreach ($i in $ICSlist )
{
$file= $i. fullname
$data = #{}
$content = Get-Content $file -Encoding UTF8
$content |
foreach-Object {
if($_.Contains(':')){
$z=#{ $_.split( ':')[0] =( $_.split( ':')[1]).Trim()}
$data. Add( $z. Keys, $z. Values)
}
}
$outlook = new-object -com Outlook.Application
$calendar = $outlook.Session.GetDefaultFolder(9)
$appt = $calendar.Items.Add(1)
$Body=[regex]::match($content,'(?<=\DESCRIPTION:).+(?=\DTEND:)', "singleline").value .trim ()
$Body= $Body -replace "\r\n\s"
$Body = $Body.replace("\,",",").replace("\n"," ")
$Body= $Body -replace "\s\s"
$Start = ($data.getEnumerator() | ?{ $_.Name -eq "DTSTART"}).Value -replace "T"
$Start = [datetime]::ParseExact ($Start ,"yyyyMMddHHmmss" ,$null )
$End = ($data.getEnumerator() | ?{ $_.Name -eq "DTEND"}).Value -replace "T"
$End = [datetime]::ParseExact ($End ,"yyyyMMddHHmmss" ,$null )
$Subject = ($data.getEnumerator() | ?{ $_.Name -eq "SUMMARY"}).Value
$Location = ($data.getEnumerator() | ?{ $_.Name -eq "LOCATION"}).Value
$appt.Start = $Start
$appt.End = $End
$appt.Subject = $Subject
$appt.Categories = "Presentations" #Pick your own category!
$appt.BusyStatus = 0 # 0=Free
$appt.Location = $Location
$appt.Body = $Body
$appt.ReminderMinutesBeforeStart = 15 #Customize if you want
$appt.Save()
if ($appt.Saved)
{ write-host "Appointment saved."}
Else {write-host "Appointment NOT saved."}
}
Acknowledging "thescriptkeeper.wordpress.com" for the script

Find file extension in folder and sum total size

I am trying to find all file extension in a folder and subfolders and generate a list. I found a oneliner but it do not generate the list as i want. i got mutiple of paths so i do this.
$date = get-date -Format d
$File = "C:\NoBackup\FolderPaths.txt"
foreach ($Folder in (Get-Content $File)) {
Get-ChildItem $Share -Recurse -ErrorAction SilentlyContinue | Group-Object extension | Select-Object #{Name="Folder";Expression={$Folder}}, name, #{n='TotalSize';e={$_.group | ForEach-Object -Begin {$size=0} -Process {$size += ([decimal]::round($_.Length / 1MB))} -End {"$size MB"}}} | Sort-Object -Property 'TotalSize' -Descending | Format-Table -AutoSize
}
This will give a new header foreach folder in folderpaths, and i need the result be like this
.ext1 .ext2 .ext3 .ext4
D:\Folder1 5MB 12MB 20MB 8MB
D:\Folder2 10MB 54MB 12MB 3MB
D:\Folder3 2MB 12MB 20MB 100MB
I cant find out to rewrite the code to get what i need. Hope you can help me out with this.
The script works now. I needed to change
foreach($folder in $folders)
To
foreach($folder in (Get-Content $file))
It is not short or sweet, but try this:
function ConvertTo-Units($decimal)
{
$value = [decimal]::Round($decimal/1mb,2)
$units = "MB"
if($value -eq 0)
{
$value = [decimal]::Round($decimal/1kb,2)
$units = "KB"
}
return "{0} {1}" -f $value,$units
}
$File = "C:\NoBackup\FolderPaths.txt"
$fileData = #{}
foreach ($folder in (Get-Content $file))
{
$files = Get-ChildItem $folder -Recurse -ErrorAction SilentlyContinue -File
$fileData[$folder] = $files | Select-Object extension,length | %{$h = #{}} { $h[$_.extension] += $_.length } { $h}
}
$extensions = $fileData.Keys | % { $fileData[$_].Keys } | % tolower | Select-Object -Unique | ? { $_ }
$properties = #(
#{Name="Folder";Expression={$_}}
)
$extensions | % {$properties += #{Name="$_";Expression=[scriptblock]::Create("ConvertTo-Units `$fileData[`$folder][""$_""]")}}
$result = #()
foreach($folder in $folders)
{
$result += $folder | Select-Object $properties
}
$result | ft * -AutoSize -force

Resources