Check if string is X509Store path or PFX file - powershell

I need to check if a string is a certificate store (eg. "Cert:\CurrentUser\My") or a pfx file path (eg. "D:\\PFXfiles\self-signed.pfx")
Which method is better to use and why? What are the cons/pros for each? Is there a better method?
Method 1:
if ($certLocation.ToUpper().Contains(".PFX"))
{
#it's a .pfx file
}
else
{
#it's a cert store
}
Method 2:
if ((Resolve-Path -LiteralPath $certLocation).Provider.Name -eq "FileSystem")
{
#it's a .pfx file
}
elseif ((Resolve-Path -LiteralPath $certLocation).Provider.Name -eq "Certificate"
{
#it's a cert store
}

I'd use Split-Path:
switch ((Split-Path $certLocation -Qualifier)) {
'cert:' { 'cert store' }
'c:' { 'file path' }
default { Write-Error "invalid provider: $_" }
}
Check the extension inside the 'file path' script block if required.

you should see magic number of file , I recommend to you use file command exist in linux and the programmer provide for windows see this link
see my example
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\Users\soheil\Desktop\1.pfx
c:\Users\soheil\Desktop\1.pfx; data
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\Users\soheil\Desktop\2.pfx
c:\Users\soheil\Desktop\2.pfx; empty
or like this
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\a.txt
c:\a.txt; UTF-8 Unicode (with BOM) English text, with very long lines, with CRLF
line terminators
first 1.pfx i create self sign with IIS
second 2.pfx i rename txt file to 2.pfx
if you want exactly understand what file is you should use file command for see magic number

To me, testing the string would be better because it's more efficient to just manipulate the string vs resolving the path, creating another object and then reading a property of on that object, but in reality it's not going to change anything. I'd do it a little differently though.
if ($certLocation.Split(":")[0] -like "cert") {
#it's a cert store
}
else {
#it's a pfx
}

I'll chime in... If you are testing a string to see where the path lies use the Resolve-Path cmdlet, and select the Provider property.
$StringPath = "Cert:\CurrentUser\my","C:\Temp\fakecert.pfx"
Switch($StringPath){
{(Resolve-Path $_|Select -Expand Provider).tostring() -eq "Microsoft.PowerShell.Security\Certificate"} {"$_ is in the Certificate Store";continue}
{(Resolve-Path $_|Select -Expand Provider).tostring() -eq "Microsoft.PowerShell.Core\FileSystem"} {"$_ is in the File System"}
}
Cert:\CurrentUser\my is in the Certificate Store
C:\Temp\fakecert.pfx is in the File System
That way PowerShell will tell you who it used to resolve the path. This will throw errors if you provide invalid paths, but should give you accurate info as to where items are stored. Error catching could be added to catch invalid paths, but that's up to you.

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.

Import-PfxCertificate no-ops, but no error or anything

I'm trying to import a PFX file into the local certificate store. However, Import-PfxCertificate just does nothing at all. No return value, no error, nothing:
I can double click on the PFX file in Explorer and import it with the same password, which works. Something about the PowerShell CmdLet isn't working. I've also tried other stores, such as Cert:\LocalMachine\My and TrustedPeople. Running it with -Verbose -Debug doesn't show anything extra. Nothing in the Application or Security event logs either. I'm also running as an admin. Ideas?
The Pfx file might have a cert chain. Treating it as a collection would be a better way of handling the certificate store. See installing cert chain for the C# this was based off;
[string] $certPath = '.\test.pfx';
[string] $certPass = 'MyPassword';
# Create a collection object and populate it using the PFX file
$collection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new();
$collection.Import($certPath, $certPass, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet);
try {
# Open the Store My/Personal
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My');
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
foreach ($cert in $collection) {
Write-Host ("Subject is: '{0}'" -f $cert.Subject )
Write-Host ("Issuer is: '{0}'" -f $cert.Issuer )
# Import the certificate into an X509Store object
# source https://support.microsoft.com/en-au/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard-net-applic
if ($cert.Thumbprint -in #($store.Certificates | % { $_.Thumbprint } )) {
Write-Warning "Certificate is already in the store"
# Force the removal of the certificate so we have no conflicts, not required if this is the first install
$store.Remove($cert)
}
# Add in the certificate
$store.Add($cert);
}
} finally {
if($store) {
# Dispose of the store once we are done
$store.Dispose()
}
}

removing file versions from the file

I am trying to write down the powershell script which should remove the item version or language version "en" from all the files located in folder tour, if it finds "Title" section in the file.
My script is working only on top file name in the loop whose title is empty but not on other files in the loop whose title are empty as well. How can I change this script so that it can scroll through out the loop and remove the language version from each of these different files whose title is empty?
$SN1 = Get-ItemProperty -Path "master:/content/www/home/company/tour/*" -Name "Title"
$SN2 = ''
$SN3 =
foreach ($SItem in $SN1.Title) {
if ("$SItem" -eq $SN2)
{
Remove-ItemVersion -Path "master:/content/www/home/company/tour/*" -Language "en"
}
}
I'm not sure about the path you are selecting, Get-ItemProperty cannot use an * in its path to select every item in the path. You must get the items first and then pipe them into the Get-ItemProperty.
You need to create an array of every item and its properties and then loop through those.
$SN1 = Get-ChildItem -Path ""master:/content/www/home/company/tour" | Get-ItemProperty | select *
Then loop through each item:
$DebugPreference = 'Continue'
foreach ($SItem in $SN1)
{
Write-Debug -Message "Checking item $($SItem.Name) for a title."
if(!($SItem.Title))
{
Write-Debug -Message "A title does not exist for this item $($SItem.FullName), removing the language property of its ItemVersion."
Remove-ItemVersion -Path $SItem.FullName -Language "en"
}
}
As a note you'll want to use -Recurse on the Get-ChildItem if you have subdirectories you want to search through as well.
Looks like your basic syntax skills may need some additional study as for some reason you have quotes around the variable you are testing and are incorrectly using syntax for the title.
So first of all, change:
if ("$SItem" -eq $SN2)
To:
if (!($SItem))
Then, SN1 is ONLY the title as you have it written, so trying to do $SN1.title simply won't work. Also, what is the source you are querying?
EDIT: ANSWER BELOW:
Now that you provided the object type it is clear. Here is the code but you need to verify WHAT is getting deleted is the correct key/value:
$SN1 = Get-ItemProperty -Path "master:/content/www/home/company/tour/*" -Name "Title"
if ($SN1.Title -eq '')
{
Remove-ItemVersion -Path "master:/content/www/home/company/tour/*" -Language "en"
}
Unless I am missing something, based on the Get-Member $SN1 is a SINGLE item so there is no need to loop anything. If there are more than one somewhere, I have not seen indication in your code so you will need to provide more detail. This command reads the single Title, and if it is blank, go delete something else. For a COLLECTION of Titles, you need to provide where they live etc. for more help.

Get actual path from path with wildcard

When using Test-Path in an if statement, I am looking to get the path that the if statement succeeds with.
For example, these files exist in C:
C:\Test6_1_15.txt
C:\Test6_2_15.txt
C:\Test6_3_15.txt
C:\Test6_4_15.txt
what do I do in the "then" branch?
$Path = "C:\Test6_*_15.txt"
if (Test-Path $Path)
{
# if test passes because there are 4 files that fit the test, but I want to be
# able to output the file that made the if statement succeed.
}
Sounds like you want Resolve-Path:
if(($Paths = #(Resolve-Path "C:\Test6_*_15.txt"))){
foreach($file in $Paths){
# do stuff
}
} else {
# Resolve-Path was unable to resolve "C:\Test6_*_15.txt" to anything
}
You can do get-item $path, that will return actual file name(s) in its result.
You will not get that with Test-Path. Test-Path returns a boolean value(s) representing the presence of the path(s) passed. Looking at the description from TechNet
It returns TRUE ($true) if all elements exist and FALSE ($false) if any are missing
If you just want the actual filenames that match then use Get-Item as it supports standard wildcards. You can get information from the System.IO.FileInfo objects that Get-Item returns.

How can I edit the settings.dat file for Windows Store Apps in Powershell?

I'm automating the deployment of a Windows Store App and I'd like to automatically set one of the settings that a user would normally configure in the settings charm. I've learned that the settings are stored in a settings.dat file, which can be opened in the registry. But this is a binary file and I have no idea how I would edit the setting I want through Powershell. Is this even something I can do or is the effort not worth it? Thanks.
This is what the particular setting looks like regedit
AFAIK there's no way to edit registry files when they are not loaded into the registry. I played with it for a while and found a way to do it, but you need to temporarily load the registry file into your registry and edit it there. It seems you need to use reg.exe to do this.
The other problem are custom property types which are used in this registry file (5f5e10c instead of e.g. REG_BINARY). Both PowerShell and .NET APIs don't seem to be able to load them or correctly save them. I had to export the keys, edit them in .reg file and import them back.
Another peculiarity to take into account is the timestamp included in all the encoded values, as described in this blog post.
Here's a working script I managed to write with additional explanation in the comments (you need to run it as administrator or loading the registry file will fail):
# full path to the registry file to edit
$settingsFile = "c:\Users\Damir\AppData\Local\Packages\113f4f59-2aa3-455b-8531-185f633c1ffe_ecet6zh215f6e\Settings\settings.dat"
# setting name to change
$settingKey = "ServerUrl"
# new setting value
$newValue = "http://prodserver.local/esign/"
# name of temporary .reg file
$regFile = ".\settings.reg"
# temporary registry location to import registry file into
$registryImportLocation = "HKLM\_TMP"
# prefix matching the setting in the .reg file
$settingKeyPattern = """$settingKey""="
# load the settings file into registry
reg load $registryImportLocation $settingsFile
# export the settings into .reg file
reg export $registryImportLocation $regFile
$fileContents = Get-Content $regFile
$finalContents = #()
$processing = $false
Foreach ($line in $fileContents)
{
If (-Not ($processing))
{
If ($line.StartsWith($settingKeyPattern))
{
# setting key found, stop copying file contents
$processing = $true
# read key value without its name
$oldSerializedValue = $line.Replace($settingKeyPattern, "")
}
Else
{
# setting key not found yet, copy old file contents to output
$finalContents += $line
}
}
else
{
# value can span multiple lines, trim leading spaces from non-first lines
$oldSerializedValue += $line.TrimStart(" ")
}
If ($processing)
{
If ($oldSerializedValue.EndsWith("\"))
{
# trailing \ indicates non-final line with key value
$oldSerializedValue = $oldSerializedValue.TrimEnd("\")
}
Else
{
# split key type and value
$match = $oldSerializedValue -match '(.*:)(.*)'
# final 8 bytes are timestamp of the value - don't modify
$timestamp = $matches[2].Substring($matches[2].Length - 23)
# encode new value in UTF-16
$newValueInBytes = [System.Text.Encoding]::Unicode.GetBytes($newValue)
# key name and type
$newValue = $settingKeyPattern + $matches[1]
# encode byte array into required string format
Foreach ($byte in $newValueInBytes)
{
$newValue += [System.Convert]::ToString($byte, 16).PadLeft(2, "0") + ","
}
# end null character string terminator
$newValue += "00,00," + $timestamp
$finalContents += $newValue
# reenable copying of the remaining file
$processing = $false
}
}
}
# dump new file contents to file
$finalContents | Out-File $regFile
# import the changed value into registry
reg import $regFile
# onload the registry file from registry
reg unload $registryImportLocation
# delete temporary file
Remove-Item $regFile
You'll need to modify it a bit to include it in your deployment process, but it should get you started.
EDIT: I wrote an accompanying blog post describing the thought process behind the answer. It provides an even more in depth explanation and links to a PowerShell function implementation wrapping the above script.