How to load variables automatically in PowerShell for every session? [duplicate] - powershell

On Windows, not counting ISE or x86, there are four (4) profile scripts.
AllUsersAllHosts # C:\Program Files\PowerShell\6\profile.ps1
AllUsersCurrentHost # C:\Program Files\PowerShell\6\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts # C:\Users\lit\Documents\PowerShell\profile.ps1
CurrentUserCurrentHost # C:\Users\lit\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
On Linux with pwsh 6.2.0 I can find only two locations.
CurrentUserAllHosts # ~/.config/powershell/Microsoft.PowerShell_profile.ps1
CurrentUserCurrentHost # ~/.config/powershell/profile.ps1
Are there any "AllUsers" profile scripts on Linux? If so, where are they?

tl;dr (also applies to Windows):
The conceptual about_Profiles help topic describes PowerShell's profiles (initialization files).
The automatic $PROFILE variable contains a string that is the path of the initialization file for the current user and the current PowerShell host environment (typically, the terminal a.k.a console).
Additional profile files are defined - along the dimensions of (a) all-users vs. current-user and (b) all host environments vs. the current one - which are exposed via properties that the $PROFILE string variable is decorated with, which makes them nontrivial to discover - see below.
None of the profile files exist by default, and in some case even their parent directories may not; the bottom section of this answer shows programmatic on-demand creation and updating of the $PROFILE file.
Olaf provided the crucial pointer in comment:
$PROFILE | select * # short for: $profile | Select-Object -Property *
shows all profile file locations, whether or not the individual profile files exist.
E.g., on my Ubuntu machine with PowerShell installed in /home/jdoe/.powershell, I get:
AllUsersAllHosts : /home/jdoe/.powershell/profile.ps1
AllUsersCurrentHost : /home/jdoe/.powershell/Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : /home/jdoe/.config/powershell/profile.ps1
CurrentUserCurrentHost : /home/jdoe/.config/powershell/Microsoft.PowerShell_profile.ps1
Length : 62
Note the presence of the [string] type's native Length property, which you could omit if you used $PROFILE | select *host* instead.
That you can get the profile locations that way is not obvious, given that $PROFILE is a string variable (type [string]).
PowerShell decorates that [string] instance with NoteProperty members reflecting all profile locations, which is why select (Select-Object) is able to extract them.
Outputting just $PROFILE - i.e. the string value - yields /home/jdoe/.config/powershell/Microsoft.PowerShell_profile.ps1, i.e. the same path as its CurrentUserCurrentHost property, i.e. the path of the user-specific profile file specific to the current PowerShell host environment (typically, the terminal aka console).[1]
You can verify the presence of these properties with reflection as follows, (which reveals their values too):
$PROFILE | Get-Member -Type NoteProperty
This means that you can also use regular property access and tab completion to retrieve individual profile locations; e.g.:
# Use tab-completion to find a specific profile location.
# Expands to .Length first, then cycles through the profile-location properties.
$profile.<tab>
# Open the all-users, all-hosts profiles for editing.
# Note: Only works if the file already exists.
# Also, you must typically run as admin to modify all-user profiles.
Invoke-Item $profile.AllUsersAllHosts
Convenience functions for getting profile locations and opening profiles for editing:
The code below defines:
Get-Profile enumerates profiles, showing their location and whether they exist on a given machine.
Edit-Profile opens profile(s) for editing (use -Force to create them on demand); note that modifying all-user profiles typically requires running as admin.
function Get-Profile {
<#
.SYNOPSIS
Gets the location of PowerShell profile files and shows whether they exist.
#>
[CmdletBinding(PositionalBinding=$false)]
param (
[Parameter(Position=0)]
[ValidateSet('AllUsersAllHosts', 'AllUsersCurrentHost', 'CurrentUserAllHosts', 'CurrentUserCurrentHost')]
[string[]] $Scope
)
if (-not $Scope) {
$Scope = 'AllUsersAllHosts', 'AllUsersCurrentHost', 'CurrentUserAllHosts', 'CurrentUserCurrentHost'
}
foreach ($thisScope in $Scope) {
[pscustomobject] #{
Scope = $thisScope
FilePath = $PROFILE.$thisScope
Exists = (Test-Path -PathType Leaf -LiteralPath $PROFILE.$thisScope)
}
}
}
function Edit-Profile {
<#
.SYNOPSIS
Opens PowerShell profile files for editing. Add -Force to create them on demand.
#>
[CmdletBinding(PositionalBinding=$false, DefaultParameterSetName='Select')]
param (
[Parameter(Position=0, ValueFromPipelineByPropertyName, ParameterSetName='Select')]
[ValidateSet('AllUsersAllHosts', 'AllUsersCurrentHost', 'CurrentUserAllHosts', 'CurrentUserCurrentHost')]
[string[]] $Scope = 'CurrentUserCurrentHost'
,
[Parameter(ParameterSetName='All')]
[switch] $All
,
[switch] $Force
)
begin {
$scopes = New-Object Collections.Generic.List[string]
if ($All) {
$scopes = 'AllUsersAllHosts', 'AllUsersCurrentHost', 'CurrentUserAllHosts', 'CurrentUserCurrentHost'
}
}
process {
if (-not $All) { $scopes.Add($Scope) }
}
end {
$filePaths = foreach ($sc in $scopes) { $PROFILE.$sc }
$extantFilePaths = foreach ($filePath in $filePaths) {
if (-not (Test-Path -LiteralPath $filePath)) {
if ($Force) {
if ((New-Item -Force -Type Directory -Path (Split-Path -LiteralPath $filePath)) -and (New-Item -Force -Type File -Path $filePath)) {
$filePath
}
} else {
Write-Verbose "Skipping nonexistent profile: $filePath"
}
} else {
$filePath
}
}
if ($extantFilePaths.Count) {
Write-Verbose "Opening for editing: $extantFilePaths"
Invoke-Item -LiteralPath $extantFilePaths
} else {
Write-Warning "The implied or specified profile file(s) do not exist yet. To force their creation, pass -Force."
}
}
}
[1] PowerShell considers the current-user, current-host profile the profile of interest, which is why $PROFILE's string value contains that value. Note that in order to decorate a [string] instance with note properties, Add-Member alone is not enough; you must use the following idiom: $decoratedString = $string | Add-Member -PassThru propName propValue - see the Add-Member help topic.

Related

Remove-item isn't removing non-empty folders [duplicate]

When using the Remove-Item command, even utilizing the -r and -Force parameters, sometimes the following error message is returned:
Remove-Item : Cannot remove item C:\Test Folder\Test Folder\Target: The directory is not empty.
Particularly, this happens when the directory to be removed is opened in Windows Explorer.
Now, while it is possible to avoid this simply by having Windows Explorer closed or not browsing that location, I work my scripts in a multi-user environment where people sometimes just forget to close Windows Explorer windows, I am interested in a solution for deleting entire folders and directories even if they are opened in Windows Explorer.
Is there an option more powerful than -Force that I can set to achieve this?
To reliably reproduce this, create the folder C:\Test Folder\Origin and populate it with some files and subfolders (important), then take the following script or one like it and execute it once. Now open one of the subfolders of C:\Test Folder\Target (in my case, I used C:\Test Folder\Target\Another Subfolder containing A third file.txt), and try running the script again. You will now get the error. If you run the script a third time, you will not get the error again (depending on circumstances that I have yet to determine, though, the error sometimes occurs the second time and then never again, and at other times it occurs every second time).
$SourcePath = "C:\Test Folder\Origin"
$TargetPath = "C:\Test Folder\Target"
if (Test-Path $TargetPath) {
Remove-Item -r $TargetPath -Force
}
New-Item -ItemType directory -Path $TargetPath
Copy-Item $SourcePath -Destination $TargetPath -Force -Recurse -Container
Update: Starting with (at least [1]) Windows 10 version 20H2 (I don't know that Windows Server version and build that corresponds to; run winver.exe to check your version and build), the DeleteFile Windows API function now exhibits synchronous behavior on supported file-systems, including NTFS, which implicitly solves the problems with PowerShell's Remove-Item and .NET's System.IO.File.Delete / System.IO.Directory.Delete (but, curiously, not with cmd.exe's rd /s).
This is ultimately only a timing issue: the last handle to a subdirectory may not be closed yet at the time an attempt is made to the delete the parent directory - and this is a fundamental problem, not restricted to having File Explorer windows open:
Incredibly, the Windows file and directory removal API is asynchronous: that is, by the time the function call returns, it is not guaranteed that removal has completed yet.
Regrettably, Remove-Item fails to account for that - and neither do cmd.exe's rd /s and .NET's [System.IO.Directory]::Delete() - see this answer for details.
This results in intermittent, unpredictable failures.
The workaround comes courtesy of in this YouTube video (starts at 7:35), a PowerShell implementation of which is below:
Synchronous directory-removal function Remove-FileSystemItem:
Important:
The synchronous custom implementation is only required on Windows, because the file-removal system calls on Unix-like platforms are synchronous to begin with. Therefore, the function simply defers to Remove-Item on Unix-like platforms. On Windows, the custom implementation:
requires that the parent directory of a directory being removed be writable for the synchronous custom implementation to work.
is also applied when deleting directories on any network drives.
What will NOT prevent reliable removal:
File Explorer, at least on Windows 10, does not lock directories it displays, so it won't prevent removal.
PowerShell doesn't lock directories either, so having another PowerShell window whose current location is the target directory or one of its subdirectories won't prevent removal (by contrast, cmd.exe does lock - see below).
Files opened with FILE_SHARE_DELETE / [System.IO.FileShare]::Delete (which is rare) in the target directory's subtree also won't prevent removal, though they do live on under a temporary name in the parent directory until the last handle to them is closed.
What WILL prevent removal:
If there's a permissions problem (if ACLs prevent removal), removal is aborted.
If an indefinitely locked file or directory is encountered, removal is aborted. Notably, that includes:
cmd.exe (Command Prompt), unlike PowerShell, does lock the directory that is its current directory, so if you have a cmd.exe window open whose current directory is the target directory or one of its subdirectories, removal will fail.
If an application keeps a file open in the target directory's subtree that was not opened with file-sharing mode FILE_SHARE_DELETE / [System.IO.FileShare]::Delete (using this mode is rare), removal will fail. Note that this only applies to applications that keep files open while working with their content. (e.g., Microsoft Office applications), whereas text editors such as Notepad and Visual Studio Code, by contrast, do not keep they've loaded open.
Hidden files and files with the read-only attribute:
These are quietly removed; in other words: this function invariably behaves like Remove-Item -Force.
Note, however, that in order to target hidden files / directories as input, you must specify them as literal paths, because they won't be found via a wildcard expression.
The reliable custom implementation on Windows comes at the cost of decreased performance.
function Remove-FileSystemItem {
<#
.SYNOPSIS
Removes files or directories reliably and synchronously.
.DESCRIPTION
Removes files and directories, ensuring reliable and synchronous
behavior across all supported platforms.
The syntax is a subset of what Remove-Item supports; notably,
-Include / -Exclude and -Force are NOT supported; -Force is implied.
As with Remove-Item, passing -Recurse is required to avoid a prompt when
deleting a non-empty directory.
IMPORTANT:
* On Unix platforms, this function is merely a wrapper for Remove-Item,
where the latter works reliably and synchronously, but on Windows a
custom implementation must be used to ensure reliable and synchronous
behavior. See https://github.com/PowerShell/PowerShell/issues/8211
* On Windows:
* The *parent directory* of a directory being removed must be
*writable* for the synchronous custom implementation to work.
* The custom implementation is also applied when deleting
directories on *network drives*.
* If an indefinitely *locked* file or directory is encountered, removal is aborted.
By contrast, files opened with FILE_SHARE_DELETE /
[System.IO.FileShare]::Delete on Windows do NOT prevent removal,
though they do live on under a temporary name in the parent directory
until the last handle to them is closed.
* Hidden files and files with the read-only attribute:
* These are *quietly removed*; in other words: this function invariably
behaves like `Remove-Item -Force`.
* Note, however, that in order to target hidden files / directories
as *input*, you must specify them as a *literal* path, because they
won't be found via a wildcard expression.
* The reliable custom implementation on Windows comes at the cost of
decreased performance.
.EXAMPLE
Remove-FileSystemItem C:\tmp -Recurse
Synchronously removes directory C:\tmp and all its content.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium', DefaultParameterSetName='Path', PositionalBinding=$false)]
param(
[Parameter(ParameterSetName='Path', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string[]] $Path
,
[Parameter(ParameterSetName='Literalpath', ValueFromPipelineByPropertyName)]
[Alias('PSPath')]
[string[]] $LiteralPath
,
[switch] $Recurse
)
begin {
# !! Workaround for https://github.com/PowerShell/PowerShell/issues/1759
if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Ignore) { $ErrorActionPreference = 'Ignore'}
$targetPath = ''
$yesToAll = $noToAll = $false
function trimTrailingPathSep([string] $itemPath) {
if ($itemPath[-1] -in '\', '/') {
# Trim the trailing separator, unless the path is a root path such as '/' or 'c:\'
if ($itemPath.Length -gt 1 -and $itemPath -notmatch '^[^:\\/]+:.$') {
$itemPath = $itemPath.Substring(0, $itemPath.Length - 1)
}
}
$itemPath
}
function getTempPathOnSameVolume([string] $itemPath, [string] $tempDir) {
if (-not $tempDir) { $tempDir = [IO.Path]::GetDirectoryName($itemPath) }
[IO.Path]::Combine($tempDir, [IO.Path]::GetRandomFileName())
}
function syncRemoveFile([string] $filePath, [string] $tempDir) {
# Clear the ReadOnly attribute, if present.
if (($attribs = [IO.File]::GetAttributes($filePath)) -band [System.IO.FileAttributes]::ReadOnly) {
[IO.File]::SetAttributes($filePath, $attribs -band -bnot [System.IO.FileAttributes]::ReadOnly)
}
$tempPath = getTempPathOnSameVolume $filePath $tempDir
[IO.File]::Move($filePath, $tempPath)
[IO.File]::Delete($tempPath)
}
function syncRemoveDir([string] $dirPath, [switch] $recursing) {
if (-not $recursing) { $dirPathParent = [IO.Path]::GetDirectoryName($dirPath) }
# Clear the ReadOnly attribute, if present.
# Note: [IO.File]::*Attributes() is also used for *directories*; [IO.Directory] doesn't have attribute-related methods.
if (($attribs = [IO.File]::GetAttributes($dirPath)) -band [System.IO.FileAttributes]::ReadOnly) {
[IO.File]::SetAttributes($dirPath, $attribs -band -bnot [System.IO.FileAttributes]::ReadOnly)
}
# Remove all children synchronously.
$isFirstChild = $true
foreach ($item in [IO.directory]::EnumerateFileSystemEntries($dirPath)) {
if (-not $recursing -and -not $Recurse -and $isFirstChild) { # If -Recurse wasn't specified, prompt for nonempty dirs.
$isFirstChild = $false
# Note: If -Confirm was also passed, this prompt is displayed *in addition*, after the standard $PSCmdlet.ShouldProcess() prompt.
# While Remove-Item also prompts twice in this scenario, it shows the has-children prompt *first*.
if (-not $PSCmdlet.ShouldContinue("The item at '$dirPath' has children and the -Recurse switch was not specified. If you continue, all children will be removed with the item. Are you sure you want to continue?", 'Confirm', ([ref] $yesToAll), ([ref] $noToAll))) { return }
}
$itemPath = [IO.Path]::Combine($dirPath, $item)
([ref] $targetPath).Value = $itemPath
if ([IO.Directory]::Exists($itemPath)) {
syncremoveDir $itemPath -recursing
} else {
syncremoveFile $itemPath $dirPathParent
}
}
# Finally, remove the directory itself synchronously.
([ref] $targetPath).Value = $dirPath
$tempPath = getTempPathOnSameVolume $dirPath $dirPathParent
[IO.Directory]::Move($dirPath, $tempPath)
[IO.Directory]::Delete($tempPath)
}
}
process {
$isLiteral = $PSCmdlet.ParameterSetName -eq 'LiteralPath'
if ($env:OS -ne 'Windows_NT') { # Unix: simply pass through to Remove-Item, which on Unix works reliably and synchronously
Remove-Item #PSBoundParameters
} else { # Windows: use synchronous custom implementation
foreach ($rawPath in ($Path, $LiteralPath)[$isLiteral]) {
# Resolve the paths to full, filesystem-native paths.
try {
# !! Convert-Path does find hidden items via *literal* paths, but not via *wildcards* - and it has no -Force switch (yet)
# !! See https://github.com/PowerShell/PowerShell/issues/6501
$resolvedPaths = if ($isLiteral) { Convert-Path -ErrorAction Stop -LiteralPath $rawPath } else { Convert-Path -ErrorAction Stop -path $rawPath}
} catch {
Write-Error $_ # relay error, but in the name of this function
continue
}
try {
$isDir = $false
foreach ($resolvedPath in $resolvedPaths) {
# -WhatIf and -Confirm support.
if (-not $PSCmdlet.ShouldProcess($resolvedPath)) { continue }
if ($isDir = [IO.Directory]::Exists($resolvedPath)) { # dir.
# !! A trailing '\' or '/' causes directory removal to fail ("in use"), so we trim it first.
syncRemoveDir (trimTrailingPathSep $resolvedPath)
} elseif ([IO.File]::Exists($resolvedPath)) { # file
syncRemoveFile $resolvedPath
} else {
Throw "Not a file-system path or no longer extant: $resolvedPath"
}
}
} catch {
if ($isDir) {
$exc = $_.Exception
if ($exc.InnerException) { $exc = $exc.InnerException }
if ($targetPath -eq $resolvedPath) {
Write-Error "Removal of directory '$resolvedPath' failed: $exc"
} else {
Write-Error "Removal of directory '$resolvedPath' failed, because its content could not be (fully) removed: $targetPath`: $exc"
}
} else {
Write-Error $_ # relay error, but in the name of this function
}
continue
}
}
}
}
}
[1] I've personally verified that the issue is resolved in version 20H2, by running the tests in GitHub issue #27958 for hours without failure; this answer suggests that the problem was resolved as early as version 1909, starting with build 18363.657, but Dinh Tran finds that the issue is not resolved as of build 18363.1316 when removing large directory trees such as node_modules. I couldn't find any official information on the subject.

Is there a way to use the equivalent of touch in PowerShell to update timestamps of a file?

I use the Unix command touch a lot to update timestamps of any new acquired/downloaded file in my system. Many times the timestamp of the existing file is old, so it can get lost in the system. Is there a way in MS Windows PowerShell to do so. I have both PowerShell 5 as well as PowerShell 6.
You can use
$file = Get-Item C:\Intel\Logs\IntelCpHDCPSvc.log
$file.LastWriteTime = (Get-Date)
Or CreationTime or LastAcccessTime, if you prefer.
As a function
Function UpdateFileLastWriteTimeToToday() {
Param ($FileName)
$file = Get-Item $FileName
Echo "Current last write time: " $file.LastWriteTime
$file.LastWriteTime = (Get-Date)
Echo "New last write time: " $file.LastWriteTime
}
#How to use
UpdateFileLastWriteTimeToToday -FileName C:\Intel\Logs\IntelGFX.Log
With the output of
Current last write time:
Tuesday, April 16, 2019 1:09:49 PM
New last write time:
Monday, November 4, 2019 9:59:55 AM
Mark's helpful answer shows how to update a single file's last-modified timestamp.
Below is the source code for function Touch-File, which implements most of the functionality that the Unix touch utility offers in a PowerShell-idiomatic fashion, including support for -WhatIf, verbose output, and a pass-thru option.
It works in both Windows PowerShell (version 3 or higher) and PowerShell Core.
You can put the function in your $PROFILE, for instance; call Get-Help Touch-File for help.
If you want to define an alias, I recommend against naming it touch, to prevent confusion with the native touch utility on Unix-like platforms;
Set-Alias tf Touch-File would work, for instance.
Examples:
# Sets the last-modified and last-accessed timestamps for all text files
# in the current directory to the current point in time.
Touch-File *.txt
# Creates files 'newfile1' and 'newfile2' and outputs information about them as
# System.IO.FileInfo instances.
# Note the need to use "," to pass multiple paths.
Touch-File newfile1, newfile2 -PassThru
# Updates the last-modified and last-accessed timestamps for all text files
# in the current directory to midnight (the start of) of today's date.
Touch-File *.txt -DateTime (Get-Date).Date
# Sets the last-modified and last-accessed timestamps of all text files
# in the current directory back by 1 hour.
Get-Item *.txt | Touch-File -Offset '-1:0'
# Sets the last-modified and last-accessed timestamps of all files in the
# current directory to the last-modified timestamp of the current directory.
Get-ChildItem -File | Touch-File -ReferencePath . '-0:1'
Touch-File source code:
Note:
The function below is also available as an MIT-licensed Gist, and only the latter will be maintained going forward. Assuming you have looked at the linked code to ensure that it is safe (which I can personally assure you of, but you should always check), you can install it directly as follows:
irm https://gist.github.com/mklement0/82ed8e73bb1d17c5ff7b57d958db2872/raw/Touch-File.ps1 | iex
Touch is not an approved verb in PowerShell, but it was chosen nonetheless,
because none of the approved verbs can adequately convey the core functionality of
this command.
function Touch-File {
<#
.SYNOPSIS
"Touches" files.
.DESCRIPTION
Similar to the Unix touch utility, this command updates the last-modified and
last-accessed timestamps of files or creates files on
demand.
The current point in time is used by default, but you can pass a
specific timestamp with -DateTime or use an existing file or directory's
last-modified timestamp with -ReferencePath.
Alternatively, the target files' current timestamps can be adjusted with
a time span passed to -Offset.
Symbolic links are invariably followed, which means that it is a file link's
*target* whose last-modified timestamp get updated.
Note:
* This means that a *link*'s timestamp is itself never updated.
* If a link's target doesn't exist, a non-terminating error occurs.
Use -WhatIf to preview the effects of a given command, and -Verbose to see
details.
Use -PassThru to pass the touched items through, i.e., to output updated
information about them.
Note that in order to pass multiple target files / patterns as arguments
you must *comma*-separate them, because they bind to a single, array-valued
parameter.
.PARAMETER Path
The paths of one or more target files, optionally expressed
as wildcard expressions, and optionally passed via the pipeline.
.PARAMETER LiteralPath
The literal paths of one or more target files, optionally
passed via the pipeline as output from Get-ChildItem or Get-Item.
.PARAMETER DateTime
The timestamp to assign as the last-modified (last-write) and last-access
values.
By default, the current point in time is used.
.PARAMETER ReferencePath
The literal path to an existing file or directory whose last-modified
timestamp should be applied to the target file(s).
.PARAMETER Offset
A time span to apply as an offset to the target files' current last-write
timestamp.
Since the intent is to adust the current timestamps of *existing* files,
non-existent paths are ignored; that is, -NoNew is implied.
Note that positive values adjust the timestamps forward (to a more recent date),
whereas negative values adjust backwards (to an earlier date.)
Examples of strings that can be used to specify time spans:
* '-1' adjust the current timestamp backward by 1 day
* '-2:0' sets it backward by 2 hours
Alternatively, use something like -(New-TimeSpan -Days 1)
.PARAMETER NoNew
Specifies that only existing files should have their timestamps updated.
By default, literal target paths that do not refer to existing items
result in files with these paths getting *created* on demand.
A warning is issued for any non-existing input paths.
.PARAMETER PassThru
Specifies that the "touched" items are passed through, i.e. produced as this
command's output, as System.IO.FileInfo instances.
.PARAMETER Force
When wildcard expressions are passed to -Path, hidden files are matched too.
.EXAMPLE
Touch-File *.txt
Sets the last-modified and last-accessed timestamps for all text files
in the current directory to the current point in time.
.EXAMPLE
Touch-File newfile1, newfile2 -PassThru
Creates files 'newfile1' and 'newfile2' and outputs information about them as
System.IO.FileInfo instances.
Note the need to use "," to pass multiple paths.
.EXAMPLE
Touch-File *.txt -DateTime (Get-Date).Date
Updates the last-modified and last-accessed timestamps for all text files
in the current directory to midnight (the start of) of today's date.
.EXAMPLE
Get-Item *.txt | Touch-File -Offset '-1:0'
Adjusts the last-modified and last-accessed timestamps of all text files
in the current directory back by 1 hour.
.EXAMPLE
Get-ChildItem -File | Touch-File -ReferencePath .
Sets the last-modified and last-accessed timestamps of all files in the
current directory to the last-modified timestamp of the current directory.
.NOTES
"Touch" is not an approved verb in PowerShell, but it was chosen nonetheless,
because none of the approved verbs can adequately convey the core functionality
of this command.
In PowerShell *Core*, implementing this command to support multiple target
paths *as individual arguments* (as in Unix touch) would be possible
(via ValueFromRemainingArguments), but such a solution would misbehave in
Windows PowerShell.
#>
# Supports both editions, but requires PSv3+
#requires -version 3
[CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
param(
[Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Parameter(ParameterSetName = 'PathAndDateTime', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Parameter(ParameterSetName = 'PathAndRefPath', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string[]] $Path
,
[Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
[Parameter(ParameterSetName = 'LiteralPathAndDateTime', Mandatory, ValueFromPipelineByPropertyName)]
[Parameter(ParameterSetName = 'LiteralPathAndRefPath', Mandatory, ValueFromPipelineByPropertyName)]
[Alias('PSPath', 'LP')]
[string[]] $LiteralPath
,
[Parameter(ParameterSetName = 'PathAndRefPath', Mandatory)]
[Parameter(ParameterSetName = 'LiteralPathAndRefPath', Mandatory)]
[string] $ReferencePath
,
[Parameter(ParameterSetName = 'PathAndDateTime', Mandatory)]
[Parameter(ParameterSetName = 'LiteralPathAndDateTime', Mandatory)]
[datetime] $DateTime
,
[Parameter(ParameterSetName = 'Path')]
[Parameter(ParameterSetName = 'LiteralPath')]
[timespan] $Offset
,
[switch] $NoNew
,
[switch] $PassThru
,
[switch] $Force
)
begin {
Set-StrictMode -Version 1
$ErrorActionPreference = 'Continue' # We want to pass non-terminating errors / .NET method-call exceptions through.
$haveRefPath = $PSBoundParameters.ContainsKey('ReferencePath')
$haveDateTime = $PSBoundParameters.ContainsKey('DateTime')
$haveOffset = $PSBoundParameters.ContainsKey('Offset')
if ($haveOffset) { $NoNew = $true } # -NoNew is implied.
# Initialize defaults (even though they may not be used).
# Defining them unconditionally prevents strict-mode violations in pseudo-ternary conditionals.
if (-not ($haveDateTime -or $haveRefPath)) { $DateTime = [datetime]::Now }
if (-not $haveOffset) { $Offset = 0 }
# If a reference item was given, obtain its timestamp now and abort if that fails.
if ($haveRefPath) {
try {
$DateTime = (Get-Item -ErrorAction Stop $ReferencePath).LastWriteTime
}
catch {
Throw "Failed to get the reference path's last-modified timestamp: $_"
}
}
$touchedCount = 0
}
process {
$wildcardsSupported = $PSCmdlet.ParameterSetName -notlike 'LiteralPath*'
# Try to retrieve existing files.
[array] $files, $dirs =
$(
if ($wildcardsSupported) {
Get-Item -Path $Path -ErrorAction SilentlyContinue -ErrorVariable err -Force:$Force
}
else {
Get-Item -LiteralPath $LiteralPath -ErrorAction SilentlyContinue -ErrorVariable err -Force:$Force
}
).Where( { -not $_.PSIsContainer }, 'Split')
# Ignore directories among the (globbed) input paths, but issue a warning.
if ($dirs) {
Write-Warning "Ignoring *directory* path(s): $dirs"
}
# -WhatIf / -Confirm support.
# Note: The prompt message is also printed with -Verbose
$targets = ($LiteralPath, ($Path, $files.FullName)[$files.Count -gt 0])[$wildcardsSupported] -replace '^Microsoft\.PowerShell\.Core\\FileSystem::' -replace ('^' + [regex]::Escape($PWD) + '[\\/]?')
if ($targets.Count -gt 1) { $targets = "`n" + ($targets -join "`n") + "`n" }
$newDateTimeDescr = if ($haveOffset -and -not ($haveDateTime -or $haveRefPath)) { "the last-modified timestamp offset by $Offset" } else { "$($DateTime + $Offset)" }
$actionDescr = ("Updating / creating with a last-modified timestamp of $newDateTimeDescr", "Updating the last-modified timestamp to $newDateTimeDescr")[$NoNew.IsPresent]
if (-not $PSCmdlet.ShouldProcess($targets, $actionDescr)) { return }
# Try to create the files that don't yet exist - unless opt-out -NoNew was specified.
if ($err) {
if ($NoNew) {
Write-Warning "Ignoring non-existing path(s): $($err.TargetObject)"
}
else {
$files += foreach ($file in $err.TargetObject) {
Write-Verbose "Creating: $file..."
New-Item -ItemType File -Path $file -ErrorAction SilentlyContinue -ErrorVariable e
# If an error occurred - such as the parent directory of a literal target path not existing - pass it through.
# The only acceptable error is if the file has just been created, between now and the time we ran Get-Item above.
if ($e -and -not (Test-Path -PathType Leaf -LiteralPath $file)) { $e | Write-Error }
}
}
}
# Update the target files' timestamps.
foreach ($file in $files) {
# Note: If $file is a symlink, *setting* timestamp properties invariably sets the *target*'s timestamps.
# *Getting* a symlink's timestame properties, by contrast, reports the *link*'s.
# This means:
# * In order to apply an offset to the existing timestamp, we must explicitly get the *target*'s timestamp
# * With -PassThru, unfortunately - given that we don't want to quietly switch to the *target* on output -
# this means that the passed-through instance will reflect the - unmodified - *link*'s properties.
$target =
if ($haveOffset -and $file.LinkType) {
# Note: If a link's target doesn't exist, a non-terminating error occurs, which we'll pass through.
# !! Due to inconsistent behavior of Get-Item as of PowerShell Core 7.2.0-preview.5, if a broken symlink
# !! is (a) specified literally and (b) alongside at least one other path (irrespective of whether -Path or -LiteralPath is used),
# !! it generates an *error* - even though passing that path *as the only one* or *by indirect inclusion via a pattern*
# !! does NOT (it lists the non-existent target in the Name column, but doesn't error).
# !! Thus, if (a) and (b) apply, the resulting error may have caused the non-existent target to be created above,
# !! assuming that its parent directory exists.
Get-Item -Force -LiteralPath $file.Target
} else {
$file
}
if ($target) {
# Set the last-modified and (always also) the last-access timestamps.
$target.LastWriteTime = $target.LastAccessTime = if ($haveOffset) { $target.LastWriteTime + $Offset } else { $DateTime }
}
if ($PassThru) { $file }
}
$touchedCount += $files.Count
}
end {
if (-not $WhatIfPreference -and $touchedCount -eq 0) {
Write-Warning "Nothing to touch."
}
}
}

Powershell: How to find out which running services aren't part of OS and non-MS

Is there a way to find out using Powershell which running services are non-native to Windows ? In other words, services that aren't part Windows OS and are non-Microsoft. I want to find out all the services that were installed by our vendors.
Finding out running services is easy:
Get-Service | Where-Object {$_.Status -eq "Running"}
You can't do with it Get-Service alone, because even though the service-info objects it outputs have a .BinaryPathName property, that property is available in PowerShell Core only and it is not necessarily the true service binary, which is often a DLL hosted by the generic svchost.exe service host.
To find the binary file path in all cases, you must (also) query the service definitions in the registry. Once you have the file path, you can use Get-Item and the .VersionInfo property of the file-info objects returned to extract information such as the product and company name.
The Get-ServiceFileInfo function at the bottom does just that; it allows you to run commands such as:
# Return information about all services that aren't part of Windows.
# (May still include Microsoft services).
Get-ServiceFileInfo | Where ProductName -ne 'Microsoft® Windows® Operating System'
Get-ServiceFileInfo source code (PSv5+, but could be adapted to work with lower versions):
# Note: While it is possible to run without elevated privileges,
# not all file information is retrievable then.
#requires -runAsAdministrator
function Get-ServiceFileInfo {
Set-StrictMode -Version 1
Get-Service | ForEach-Object {
# PowerShell Core only:
# Get the service binary path, which may or may not be the true service
# executable.
$binaryPath = $_.BinaryPathName
# Windows PowerShell:
# We fall back on trying to obtain the "ImagePath" value from the registry.
# Note: Even in PowerShell Core there appear to be services that Get-Service fails
# to query even when running as admin, such as "SshdBroker"
# (a non-terminating error is issued).
# Reading from the registry is needed in that case too,
# but, only succeeds when running as admin.
if (-not $binaryPath) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$($_.Name)" ImagePath } catch { }
}
# Test for svchost.exe, which indicates the need to look for the service-specific DLL path elsewhere.
if ($binaryPath -like '*\svchost.exe *') {
# Get the actual binary (DLL) from the registry, subkey "Parameters", value "ServiceDLL"
# NOTE: Some services exist in *2* (multiple?) incarnations, as "<name>"" and "<name>_<num>"
# Only the "<name>" incarnation has the "ServiceDLL" value, so we fall back on that.
foreach ($keyName in $_.Name, ($_.Name -split '_')[0]) {
# NOTE: Most DLL-based services store the "ServiceDLL" value in the "Parameters" subkey, but
# some have it in the service's root key itself.
foreach ($subKeyName in "$keyName\Parameters", $keyName) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$subKeyName" ServiceDLL } catch { }
if ($binaryPath) { break }
}
}
}
# Sanitize the path:
# * Some values have enclosing "...", so we strip them,
# * others have arguments, so we only take the first token.
$binaryPath = if ($binaryPath.StartsWith('"')) {
($binaryPath -split '"')[1]
} else {
# The path / command line isn't or doesn't start with a double-quoted token, which
# can mean one of two things:
# * It is a command line based on an unquoted executable, possibly with arguments.
# * It is a service DLL path - possibly with spaces in the (expanded) path.
if (Test-Path -LiteralPath $binaryPath -Type Leaf) {
$binaryPath # Value as a whole is a file path
} else {
(-split $binaryPath)[0] # Value is a command line, extract executable
}
}
$FileVersionInfo = if ($binaryPath) { (Get-Item -LiteralPath $binaryPath).VersionInfo }
# Construct the output object.
[pscustomobject] #{
Name = $_.Name
BinaryPath = if ($binaryPath) { $binaryPath } else { '(n/a)'; Write-Error "Failed to determine binary path for service '$($_.Name)'. Try running as admin." }
ProductName = $FileVersionInfo.ProductName
FileDescription = $FileVersionInfo.FileDescription
CompanyName = $FileVersionInfo.CompanyName
}
}
}

How do I set an env variable in PowerShell if it doesn't exist?

I'm surprised that I didn't get the answer for this common scenario after Googling for while...
How can an environment variable in be set in PowerShell if it does not exist?
The following code defines environment variable FOO for the current process, if it doesn't exist yet.
if ($null -eq $env:FOO) { $env:FOO = 'bar' }
# If you want to treat a *nonexistent* variable the same as
# an existent one whose value is the *empty string*, you can simplify to:
if (-not $env:FOO) { $env:FOO = 'bar' }
# Alternatively:
if (-not (Test-Path env:FOO)) { $env:FOO = 'bar' }
# Or even (quietly fails if the variable already exists):
New-Item -ErrorAction Ignore env:FOO -Value bar
In PowerShell (Core) 7.1+, which has null-coalescing operators, you can simplify to:
$env:FOO ??= 'bar'
Note:
Environment variables are strings by definition. If a given environment variable is defined, but has no value, its value is the empty string ('') rather than $null. Thus, comparing to $null can be used to distinguish between an undefined environment variable and one that is defined, but has no value. However, note that assigning to environment variables in PowerShell / .NET makes no distinction between $null and '', and either value results in undefining (removing) the target environment variable; similarly, in cmd.exe set FOO= results in removal/non-definition of variable FOO, and the GUI dialog (accessible via sysdm.cpl) doesn't allow you to define a variable with an empty string either. However, the Windows API (SetEnvironmentVariable) does permit creating environment variables that contain the empty string.
On Unix-like platforms, empty-string values are allowed too, and the native, POSIX-compatible shells (e.g, bash and /bin/sh) - unlike PowerShell - also allow you to create them (e.g, export FOO=). Note that environment variable definitions and lookups are case-sensitive on Unix, unlike on Windows.
Note: If the environment variable is created on demand by the assignment above ($env:FOO = ...), it will exist for the current process and any child processes it creates only Thanks, PetSerAl.
The following was mostly contributed by Ansgar Wiechers, with a supplement by Mathias R. Jessen:
On Windows[*], if you want to define an environment variable persistently, you need to use the static SetEnvironmentVariable() method of the [System.Environment] class:
# user environment
[Environment]::SetEnvironmentVariable('FOO', 'bar', 'User')
# system environment (requires admin privileges)
[Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
Note that these definitions take effect in future sessions (processes), so in order to define the variable for the current process as well, run $env:FOO = 'bar' in addition, which is effectively the same as [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Process').
When using [Environment]::SetEnvironmentVariable() with User or Machine, a WM_SETTINGCHANGE message is sent to other applications to notify them of the change (though few applications react to such notifications).
This doesn't apply when targeting Process (or when assigning to $env:FOO), because no other applications (processes) can see the variable anyway.
See also: Creating and Modifying Environment Variables (TechNet article).
[*] On Unix-like platforms, attempts to target the persistent scopes - User or Machine- are quietly ignored, as of .NET (Core) 7, and this non-support for defining persistent environment variables is unlikely to change, given the lack of a unified mechanism across Unix platforms.
Code
function Set-LocalEnvironmentVariable {
param (
[Parameter()]
[System.String]
$Name,
[Parameter()]
[System.String]
$Value,
[Parameter()]
[Switch]
$Append
)
if($Append.IsPresent)
{
if(Test-Path "env:$Name")
{
$Value = (Get-Item "env:$Name").Value + $Value
}
}
Set-Item env:$Name -Value "$value" | Out-Null
}
function Set-PersistentEnvironmentVariable {
param (
[Parameter()]
[System.String]
$Name,
[Parameter()]
[System.String]
$Value,
[Parameter()]
[Switch]
$Append
)
Set-LocalEnvironmentVariable -Name $Name -Value $Value -Append:$Append
if ($Append.IsPresent) {
$value = (Get-Item "env:$Name").Value
}
if ($IsWindows) {
setx "$Name" "$Value" | Out-Null
return
}
$pattern = "\s*export[ \t]+$Name=[\w]*[ \t]*>[ \t]*\/dev\/null[ \t]*;[ \t]*#[ \t]*$Name\s*"
if ($IsLinux) {
$file = "~/.bash_profile"
$content = (Get-Content "$file" -ErrorAction Ignore -Raw) + [System.String]::Empty
$content = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, [String]::Empty);
$content += [System.Environment]::NewLine + [System.Environment]::NewLine + "export $Name=$Value > /dev/null ; # $Name"
Set-Content "$file" -Value $content -Force
return
}
if ($IsMacOS) {
$file = "~/.zprofile"
$content = (Get-Content "$file" -ErrorAction Ignore -Raw) + [System.String]::Empty
$content = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, [String]::Empty);
$content += [System.Environment]::NewLine + [System.Environment]::NewLine + "export $Name=$Value > /dev/null ; # $Name"
Set-Content "$file" -Value $content -Force
return
}
throw "Invalid platform."
}
function Set-PersistentEnvironmentVariable
Set a variable/value in actual process and system. This function calls Set-LocalEnvironmentVariable function to set process scope variables and perform task for set variables in machine scope.
On Windows you can use:
[Environment]::SetEnvironmentVariable with machine scope, user or machine don't work on Linux or MacOS
setx command
On Linux we can add export VARIABLE_NAME=Variable value to file ~/.bash_profile. For a new bash terminal the process execute these instructions located in ~/.bash_profile.
On MacOS similiar to Linux but if you have zsh terminal the file is .zprofile, if the default terminal is bash, the file is .bash_profile. In my function code we need to add detection of default terminal if you wish. I assume that default terminal is zsh.
function Set-LocalEnvironmentVariable
Set a variable/value in actual process. Using Drive env:.
Examples
#Set "Jo" value to variable "NameX", this value is accesible in current process and subprocesses, this value is accessible in new opened terminal.
Set-PersistentEnvironmentVariable -Name "NameX" -Value "Jo"; Write-Host $env:NameX
#Append value "ma" to current value of variable "NameX", this value is accesible in current process and subprocesses, this value is accessible in new opened terminal.
Set-PersistentEnvironmentVariable -Name "NameX" -Value "ma" -Append; Write-Host $env:NameX
#Set ".JomaProfile" value to variable "ProfileX", this value is accesible in current process/subprocess.
Set-LocalEnvironmentVariable "ProfileX" ".JomaProfile"; Write-Host $env:ProfileX
Output
Windows 10
Ubuntu WSL
References
Check About Environment Variables
Shell initialization files
ZSH: .zprofile, .zshrc, .zlogin - What goes where?
You can use the following code to set an environment variable in PowerShell if it doesn't exist:
if (!(Test-Path -Path Env:VAR_NAME)) {
New-Item -Path Env:VAR_NAME -Value "VAR_VALUE"
}
Replace VAR_NAME with the name of the environment variable and VAR_VALUE with the desired value.

PowerShell script to check an application that's locking a file?

Using in PowerShell, how can I check if an application is locking a file?
I like to check which process/application is using the file, so that I can close it.
You can do this with the SysInternals tool handle.exe. Try something like this:
PS> $handleOut = handle
PS> foreach ($line in $handleOut) {
if ($line -match '\S+\spid:') {
$exe = $line
}
elseif ($line -match 'C:\\Windows\\Fonts\\segoeui\.ttf') {
"$exe - $line"
}
}
MSASCui.exe pid: 5608 ACME\hillr - 568: File (---) C:\Windows\Fonts\segoeui.ttf
...
This could help you: Use PowerShell to find out which process locks a file. It parses the System.Diagnostics.ProcessModuleCollection Modules property of each process and it looks for the file path of the locked file:
$lockedFile="C:\Windows\System32\wshtcpip.dll"
Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq $lockedFile){$processVar.Name + " PID:" + $processVar.id}}}
You should be able to use the openfiles command from either the regular command line or from PowerShell.
The openfiles built-in tool can be used for file shares or for local files. For local files, you must turn on the tool and restart the machine (again, just for first time use). I believe the command to turn this feature on is:
openfiles /local on
For example (works on Windows Vista x64):
openfiles /query | find "chrome.exe"
That successfully returns file handles associated with Chrome. You can also pass in a file name to see the process currently accessing that file.
You can find a solution using Sysinternal's Handle utility.
I had to modify the code (slightly) to work with PowerShell 2.0:
#/* http://jdhitsolutions.com/blog/powershell/3744/friday-fun-find-file-locking-process-with-powershell/ */
Function Get-LockingProcess {
[cmdletbinding()]
Param(
[Parameter(Position=0, Mandatory=$True,
HelpMessage="What is the path or filename? You can enter a partial name without wildcards")]
[Alias("name")]
[ValidateNotNullorEmpty()]
[string]$Path
)
# Define the path to Handle.exe
# //$Handle = "G:\Sysinternals\handle.exe"
$Handle = "C:\tmp\handle.exe"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\b(\d+)\b)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# (?m) for multiline matching.
# It must be . (not \.) for user group.
[regex]$matchPattern = "(?m)^(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+(?<User>.+)\s+\w+:\s+(?<Path>.*)$"
# skip processing banner
$data = &$handle -u $path -nobanner
# join output for multi-line matching
$data = $data -join "`n"
$MyMatches = $matchPattern.Matches( $data )
# //if ($MyMatches.value) {
if ($MyMatches.count) {
$MyMatches | foreach {
[pscustomobject]#{
FullName = $_.groups["Name"].value
Name = $_.groups["Name"].value.split(".")[0]
ID = $_.groups["PID"].value
Type = $_.groups["Type"].value
User = $_.groups["User"].value.trim()
Path = $_.groups["Path"].value
toString = "pid: $($_.groups["PID"].value), user: $($_.groups["User"].value), image: $($_.groups["Name"].value)"
} #hashtable
} #foreach
} #if data
else {
Write-Warning "No matching handles found"
}
} #end function
Example:
PS C:\tmp> . .\Get-LockingProcess.ps1
PS C:\tmp> Get-LockingProcess C:\tmp\foo.txt
Name Value
---- -----
ID 2140
FullName WINWORD.EXE
toString pid: 2140, user: J17\Administrator, image: WINWORD.EXE
Path C:\tmp\foo.txt
Type File
User J17\Administrator
Name WINWORD
PS C:\tmp>
I was looking for a solution to this as well and hit some hiccups.
Didn't want to use an external app
Open Files requires the local ON attribute which meant systems had to be configured to use it before execution.
After extensive searching I found.
https://github.com/pldmgg/misc-powershell/blob/master/MyFunctions/PowerShellCore_Compatible/Get-FileLockProcess.ps1
Thanks to Paul DiMaggio
This seems to be pure powershell and .net / C#
You can find for your path on handle.exe.
I've used PowerShell but you can do with another command line tool.
With administrative privileges:
handle.exe -a | Select-String "<INSERT_PATH_PART>" -context 0,100
Down the lines and search for "Thread: ...", you should see there the name of the process using your path.
Posted a PowerShell module in PsGallery to discover & kill processes that have open handles to a file or folder.
It exposes functions to: 1) find the locking process, and 2) kill the locking process.
The module automatically downloads handle.exe on first usage.
Find-LockingProcess()
Retrieves process information that has a file handle open to the specified path.
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA | Get-Process
Stop-LockingProcess()
Kills all processes that have a file handle open to the specified path.
Example: Stop-LockingProcess -Path $Home\Documents
PsGallery Link: https://www.powershellgallery.com/packages/LockingProcessKiller
To install run:
Install-Module -Name LockingProcessKiller
I like what the command prompt (CMD) has, and it can be used in PowerShell as well:
tasklist /m <dllName>
Just note that you can't enter the full path of the DLL file. Just the name is good enough.
I've seen a nice solution at Locked file detection that uses only PowerShell and .NET framework classes:
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate,[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$obj = New-Object Object
$obj | Add-Member Noteproperty FilePath -value $filePath
$obj | Add-Member Noteproperty IsLocked -value $filelocked
$obj
}
If you modify the above function slightly like below it will return True or False
(you will need to execute with full admin rights)
e.g. Usage:
PS> TestFileLock "c:\pagefile.sys"
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name Filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$filelocked
}