How to work with PowerShell Files Read/Write using Set-Content & Get-Content


In the PowerShell script writing series, we are working on some of the helpful areas to write powershell scripts. In this series we would continue covering some of the important topics on working with PowerShell read files, write files,folder,subfolders.
Another area where you should become very confident is working with files (read & write), as you will need to work with them very frequently.

First, we will take a look at the basics of working with files by retrieving and writing files and the content of the files. This can be achieved with the Get-Content and Set-Content/Out-File cmdlets.

First of all, we will take a dedicated look at how you can export content to a file:

#Storing working location
$exportedProcessesPath = 'C:\temp\test.txt'

#Write processes table to file and show the result in Terminal with the -PassThru flag
Get-Process | Set-Content -Path $exportedProcessesPath 

#Open file to verify
psedit $exportedProcessesPath

#retrieving processes and exporting them to file
Get-Process | Out-File $exportedProcessesPath 

#Open file to verify
psedit $exportedProcessesPath #or use notepad to open file

#retrieving processes and exporting them to file with Out-String
Get-Process | Out-String | Set-Content $exportedProcessesPath -PassThru

#Open file to verify
psedit $exportedProcessesPath #or use notepad to open file

There is a small difference between exporting content with the two aforementioned cmdlets. Set-Content will call the ToString() method of each object, whereas Out-File will call the Out-String method first and then write the result to file.

You will have a similar result when using Out-File or Set-Content in combination with Out-String:

Sometimes, it may also be necessary to export the content with a specified encoding. There is an additional flag available to accomplish this task, as shown in the following example:


#retrieving process and exporting them to file with encoding
Get-Process | Out-String | Set-Content $exportedProcessesPath -Encoding UTF8
Get-Process | Out-String | Set-Content $exportedProcessesPath -Encoding Byte

Reading File content in powershell

Retrieving the content works very similarly to the Get-Content cmdlet. One downside of the cmdlet is that it will load the complete file into the cache. Depending on the file size, this may take very long and even become unstable. Here is an easy example to load the content into a variable:

Because of this issue, it may become necessary to only retrieve a dedicated number of lines. There are two flags available for this, as follows:

#The last five lines
Get-Content -Path $exportedProcessesPath -Tail 5

#The first five lines
Get-Content -Path $exportedProcessesPath -TotalCount 5


Improving performance of Get-content

In addition, you can also specify how many lines of content are sent through the pipeline at a time. The default value for the ReadCount flag is 0, and a value of 1 would send all content at once. This parameter directly affects the total time for the operation, and can decrease the time significantly for larger files:


#Get-Content with ReadCount, because of perfomance-improvement.
$data = (Get-Content -Path $exportedProcessesPath -ReadCount 50)

#Retrieving data as one large string
$data = Get-Content -Path $exportedProcessesPath -Raw


Working with Files,Folder,Sub-folders

The next step when working with files and folders is searching for specific ones. This can be easily achieved with the Get-ChildItem command for the specific PSDrive:


#Simple Subfolders
Get-ChildItem -Path 'C:\temp' -Directory

#Recurse
Get-ChildItem -Path 'C:\Windows' -Directory -Recurse 


#Simple Subfiles
Get-ChildItem -Path 'C:\temp' -File


#Recurse
Get-ChildItem -Path 'C:\Windows' -File -Recurse 

As you can see, you can easily work with the -Directory and -File flags to define the outcome. But you will normally not use such simple queries, as you want to filter the result in a dedicated way.


The next, more complex, example shows a recursive search for *.txt files. We are taking four different approaches to search for those file types and will compare their runtimes:


All methods retrieved the same amount of line? $($countWhere -eq $countWhereObject -eq $countInclude -eq $countCmd)

The Slow Approach!


#Define a location where txt files are included
$Dir = 'C:\temp\'

#Filtering with .Where()
$timeWhere = (Measure-Command {(Get-ChildItem $Dir -Recurse -Force -ErrorAction SilentlyContinue).Where({$_.Extension -like '*txt*'})}).TotalSeconds

$countWhere = $((Get-ChildItem $Dir -Recurse -Force -ErrorAction SilentlyContinue).Where({$_.Extension -like '*txt*'})).Count

#Filtering with Where-Object
$timeWhereObject = (Measure-Command {(Get-ChildItem $Dir -Recurse -Force -ErrorAction SilentlyContinue) | Where-Object {$_.Extension -like '*txt*'}}).TotalSeconds

$countWhereObject = $((Get-ChildItem $Dir -Recurse -Force -ErrorAction SilentlyContinue) | Where-Object {$_.Extension -like '*txt*'}).Count


The first two approaches use Get-ChildItem with filtering afterwards, which is always the slowest approach.



#Filtering with Include
$timeInclude = (Measure-Command {Get-ChildItem -Path "$($Dir)*" -Include *.txt* -Recurse}).TotalSeconds

$countInclude = $(Get-ChildItem -Path "$($Dir)*" -Include *.txt* -Recurse).Count

The third approach uses filtering within the Get-ChildItem cmdlet, using the -Include flag. This is obviously much faster than the first two approaches.


#Show all results
Write-Host @"
Filtering with .Where(): $timeWhere
Filtering with Where-Object: $timeWhereObject
Filtering with Include: $timeInclude




You will also need to create new files and folders and combine paths very frequently, which is shown in the following snippet. The subdirectories of a folder are being gathered, and one archive folder will be created underneath each one:

#user folder
$UserFolders = Get-ChildItem 'c:\users\' -Directory

#Creating archives in each subfolder
foreach ($userFolder in $UserFolders)
{
    New-Item -Path (Join-Path $userFolder.FullName ('{0}_Archive' -f $userFolder.BaseName)) -ItemType Directory -WhatIf
}


Keep in mind that, due to the PSDrives, you can simply work with the basic cmdlets such as New-Item. We made use of the -WhatIf flag to just take a look at what would have been executed. If you're not sure that your construct is working as desired, just add the flag and execute it once to see its outcome.
A best practice to combine paths is to always use Join-Path to avoid problems on different OSes or with different PSDrives. Typical errors are that you forget to add the delimiter character or you add it twice. This approach will avoid any problems and always add one delimiter.

The next typical use case you will need to know is how to retrieve file and folder sizes.


The following example retrieves the size of a single folder, optionally displaying the size for each subfolder as well.


It is written as a function to be dynamically extendable. This might be good practice for you use, in order to understand and make use of the contents of previous chapters. You can try to extend this function with additional properties and by adding functionality to it.


.SYNOPSIS
Retrieves folder size.
.DESCRIPTION
Retrieves folder size of a dedicated path or all subfolders of the dedicated path.
.EXAMPLE
Get-FolderSize -Path c:\temp\ -ShowSubFolders | Format-List
.INPUTS
Path
.OUTPUTS
Path and Sizes
.NOTES
folder size example
#>
function Get-FolderSize {
Param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
$Path,
[ValidateSet("KB","MB","GB")]
$Units = "MB",
[Switch] $ShowSubFolders = $false
)
if((Test-Path $Path) -and (Get-Item $Path).PSIsContainer )
{
if ($ShowSubFolders)
{
$subFolders = Get-ChildItem $Path -Directory
foreach ($subFolder in $subFolders)
{
$Measure = Get-ChildItem $subFolder.FullName -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
$Sum = $Measure.Sum / "1$Units"
[PSCustomObject]@{
"Path" = $subFolder
"Size($Units)" = [Math]::Round($Sum,2)
}
}
}
else
{
$Measure = Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
$Sum = $Measure.Sum / "1$Units"
[PSCustomObject]@{
"Path" = $Path
"Size($Units)" = [Math]::Round($Sum,2)
}
}
}
}


Next, we will dive into specific file types, as they hold some benefits in storing, retrieving, and writing information to file.

Creativity is just connecting things! Join Us to connect the dots!

Comments

Popular posts from this blog

Android Navigation Drawer sliding menu with fragments

Design a Beautiful Login Screen for your Android App

Android Actionbar Navigating with Swipeable Tabs