Auditing Permissions with Powershell - powershell

A little background first: I've been asked as part of a Scrum team at work to prepare a Powershell script that will collate a list of SMB Shares that have been flagged as safe to archive from the CSV files that were given to me nested in a folder structure (each share equates to one CSV file, with a list of files within, but the only relevance for this is that they are titled SHARENAME.csv), and the current permissions of each, in case they need to be restored at a later date.
At the moment, I have the following for the first step (nice and easy):
get-childitem -path 'C:\users\adam.lane\desktop\_with adam\_remove' | select BaseName | export-csv -path "..\test.csv"
This gives me a single CSV file with the names of all of the 188 shares in a single column. The second step, exporting a CSV file for each with a list of permissions, is not going as well. So far I have the following:
$share = Import-Csv -path 'C:\users\adam.lane\desktop\_with adam\test.csv' -Header 'BaseName'
ForEach ($share in $share) {
get-smbshareaccess -name $share -cimsession euukpopdfs005
}
Obviously at the moment there's no export-csv command in there, but essentially I'll want the script to use the same share name that it calls with 'get-smbshareaccess -name' as the file name for the CSV file. Unfortunately I hit 188 errors in the following format at this point:
get-smbshareaccess : euukpopdfs005: No MSFT_SMBShare objects found with property 'Name' equal to '#{BaseName=Brand}'. Verify the value of the property and retry.
At line:3 char:1
+ get-smbshareaccess -name $share -cimsession euukpopdfs005
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (#{BaseName=Brand}:String) [Get-SmbShareAccess], CimJobException
+ FullyQualifiedErrorId : CmdletizationQuery_NotFound_Name,Get-SmbShareAccess
+ PSComputerName : euukpopdfs005
So rather than calling the BaseName (for instance "Brand" in this first case), it's calling (#{BaseName=Brand}:String), which it then doesn't find anything for.
Is there anyway I can tidy this up? Ideally in some way that will work for both the Get-SmbShareAccess command and the export-csv command, but I'm not against doing a little more jiggery pokery as needed.
This is my first post, so I apologise if this is too long, lacking in detail, etc etc. Let me know if you need anything else! Thanks in advance.
Final (working) code after suggestions:
get-childitem -path 'C:\users\adam.lane\desktop\_with adam\_remove' | select BaseName | export-csv -path "..\test.csv"
$share = Import-Csv -path 'C:\users\adam.lane\desktop\_with adam\test.csv' -Header 'BaseName'
$share | Format-List
ForEach ($item in $share) {
get-smbshareaccess -name $item.BaseName -cimsession euukpopdfs005
}

Final working code (Thanks everyone!):
get-childitem -path 'C:\users\adam.lane\desktop\_with adam\_remove' | select BaseName | export-csv -path "..\test.csv"
$share = Import-Csv -path 'C:\users\adam.lane\desktop\_with adam\test.csv' -Header 'BaseName'
$share | Format-List
ForEach ($item in $share) {
get-smbshareaccess -name $item.BaseName -cimsession euukpopdfs005
}

Related

Powershell, Missing a property in a column after creating it when trying to Export-Csv

So, I'm trying to create a csv file and then append result data to it. I feel like I'm just misusing something here so I figured I'd ask.
$headerText = ('"SamAccountName","Password",' + "`n")
New-Item C:\Users\Administrator\Desktop\test.csv | Add-Content -value $headerText
#stuff happens
Get-RandoPass| Export-Csv -NoTypeInformation -Path 'C:\Users\Administrator\Desktop\test.csv' -Append
Export-Csv : Cannot append CSV content to the following file: C:\Users\Administrator\Desktop\test.csv. The
appended object does not have a property that corresponds to the following column:
SamAccountName. To continue with mismatched properties, add the -Force parameter, and
then retry the command.
At line:226 char:22
+ ... mPassword | Export-Csv -NoTypeInformation -Path 'C:\Users\Administrator\Desktop\test.csv' ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (SamAccountName:String) [Export-Csv], Inva
lidOperationException
+ FullyQualifiedErrorId : CannotAppendCsvWithMismatchedPropertyNames,Microsoft.Po
werShell.Commands.ExportCsvCommand
Always, always create and test code paths that show results one step at a time, to make sure you are getting what you'd expect before moving to the next thing.
For example:
Why are you doing this?
$headerText = ('"SamAccountName","Password",' + "`n")
'New-Item D:\temp\test.csv' | Add-Content -value $headerText
If you wrote and tested just this section, you'd see right away that this approach is not prudent.
$headerText = ('"SamAccountName","Password",' + "`n")
'New-Item D:\temp\test.csv' |
Add-Content -value $headerText -WhatIf
#Results
<#
Add-Content : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its
properties do not match any of the parameters that take pipeline input.
At line:2 char:31
+ 'New-Item D:\temp\test.csv' | Add-Content -value $headerText -WhatIf
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (New-Item D:\temp\test.csv:String) [Add-Content], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.AddContentCommand
#>
These are two separate commands, and as you can see, the pipeline is not possible. So, until you address that, nothing else matters.
$headerText = ('"SamAccountName","Password",' + "`n")
New-Item -Path 'D:\Temp' -Name test.csv -ItemType File -Force
Add-Content -Path 'D:\temp\test.csv' -Value $headerText -WhatIf
Directory: D:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 13-Aug-22 18:46 0 test.csv
What if: Performing the operation "Add Content" on target "Path: D:\temp\test.csv".
Refactor suggestion
You are overcomplicating this. There is no need for New-Item at all,
as the ADDS cmdlets will have headers. You should really just combine
the commands and output using the Export-Csv cmdlet.
Don't use shorthand/aliases in scripts. It's bad practice and makes
scripts hard to read and maintain. They are fine to use in throwaway
code of interactive command line stuff.
Take advantage of Powershell natural line break to avoid long code
lines for better readability.
I do not have ADDS handy, but something like this should get you what you are after.
Get-ADDistinguishedName -ADComputer $env:COMPUTERNAME |
ForEach-Object{
(($PSItem) -split ',' |
Where-Object {$PSItem -match '^DC'}) -join ','
}
Get-ADUser -Filter '*' -SearchBase $DNv |
Select-Object SamAccountname, #{Name = 'Password';Expression = {Get-RandoPass}} |
Export-Csv -NoTypeInformation -Path 'D:\temp\test.csv'
That formula is known as a PS calculated property. Take time to read up on them, as well as hash tables and PSCuistomObject to make your effort more efficient/proficient.
Combining output from multiple cmdlets is a common thing.
https://duckduckgo.com/?q=%27combine+adds+cmdlet+output%27&t=h_&ia=web
Update as per our comments below.
# Generate one random password
Add-Type -AssemblyName System.Web
[System.Web.Security.Membership]::GeneratePassword(15,2)
# Results
<#
_ruj5y1}jyHix1F
#>
# Generate one or more random passwords for user input
$PasswordCount = [Microsoft.VisualBasic.Interaction]::InputBox(
'Number of passwords needed:',
'Generate Passwords',
'Enter the number of passwords to generate here.'
)
Add-Type -AssemblyName System.Web
1..$PasswordCount |
ForEach-Object {[System.Web.Security.Membership]::GeneratePassword(15,2)}
# Results
<#
!KE(b]2#h#s3iHt
?aZ[E()ElN-Q88R
liTKWM|UE/Dj&3;
aUP}r8n9bBJj]nY
PuQvOPs2f/QAi6?
#>
This can easily be turned into a proper function to call in other code.
Function New-RandomPassword
{
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
[CmdletBinding(SupportsShouldProcess)]
[Alias('nrp')]
Param
(
)
Add-Type -AssemblyName System.Drawing,
PresentationCore,
PresentationFramework,
System.Windows.Forms,
Microsoft.VisualBasic,
System.Web
[System.Windows.Forms.Application]::EnableVisualStyles()
$PasswordCount = [Microsoft.VisualBasic.Interaction]::
InputBox(
'Number of passwords needed:',
'Generate Passwords',
'Enter the number of passwords to generate here.'
)
1..$PasswordCount |
ForEach-Object {[System.Web.Security.Membership]::GeneratePassword(15,2)}
}
Get-ADDistinguishedName -ADComputer $env:COMPUTERNAME |
ForEach-Object{
(($PSItem) -split ',' |
Where-Object {$PSItem -match '^DC'}) -join ','
}
Get-ADUser -Filter '*' -SearchBase $DNv |
Select-Object SamAccountname, #{Name = 'Passwords';Expression = {New-RandomPassword}} |
Export-Csv -NoTypeInformation -Path 'D:\temp\test.csv' -Append
Note:
This dialog box will show and ask for the number of passwords needed for every SamAccontname you pass to it. This is by design for any loop construct.
If you are after only asking the question once as input and for that number of passwords that are to be used for each SamAccountname passed in (which would be odd - as that is the same Passwords for everybody), then that function must be called standalone, the pass those results in the loop, not the function.
Meaning this:
$PasswordList = New-RandomPassword
Get-ADUser -Filter '*' -SearchBase $DNv |
Select-Object SamAccountname, #{Name = 'Passwords';Expression = {$PasswordList}} |
Export-Csv -NoTypeInformation -Path 'D:\temp\test.csv' -Append
Update based on your comment:
Function Get-PasswordCount
{
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
[CmdletBinding(SupportsShouldProcess)]
[Alias('gpc')]
Param
(
)
Add-Type -AssemblyName System.Drawing,
PresentationCore,
PresentationFramework,
System.Windows.Forms,
Microsoft.VisualBasic,
System.Web
[System.Windows.Forms.Application]::EnableVisualStyles()
[Microsoft.VisualBasic.Interaction]::
InputBox(
'Passwords per SamAccountname',
'Select random Passwords per account',
'Enter the number of passwords to use per SamAccountname.'
)
}
# Do not put this in a loop, run separately
($PasswordList = New-RandomPassword)
# Results
<#
{BCE!xP#-QZbbW[
>i4F5/fFR;NOJ^r
xo>%u#.rR72Raf0
...
#>
# Do not put this in a loop, run separately
($SamAccountPasswordCount = Get-PasswordCount)
# Results
<#
5
#>
# This is in the loop
($SamAccountPasswordlist = $PasswordList |
Get-Random -Count $SamAccountPasswordCount)
# Results
<#
(#rFcb|69SZR:n9
G7UiI?^bzu*Z!]1
bB>^])PxVI9q-VO
A6b_.J!gYF:7[o?
>i4F5/fFR;NOJ^r
#>
Get-ADUser -Filter '*' -SearchBase $DNv |
Select-Object SamAccountname, #{Name = 'Passwords';Expression = {$SamAccountPasswordlist}} |
Export-Csv -NoTypeInformation -Path 'D:\temp\test.csv' -Append

Catching paths of inaccessible folders from Get-Childitem

I am working on small script to capture file hashes on a running system. I only have Powershell available.
This is the active part of the code:
get-childitem -path $path -filter $filename -Recurse -Force | Select FullName | foreach-object { get-filehash $_.fullname | select * }
this is the command I am testing with:
./Get-FileHashesRecursive.ps1 -path c:\ -filename *.txt
When running the script I get a series of errors because certain folders are inaccessible. I'd like to record the paths of those folders so the user has a record on completion of what failed.
the error looks like this in a console window:
get-childitem : Access to the path 'C:\$Recycle.Bin\S-1-5-21-4167544967-4010527683-3770225279-9182' is denied.
At E:\git\Get-RemoteFileHashesRecursive\Get-FileHashesRecursive.ps1:14 char:9
+ get-childitem -path $path -filter $filename -Recurse -Force | ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\$Recycle.Bin...3770225279-9182:String) [Get-ChildItem], UnauthorizedAccessException
+ FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
Is there a way I can grab the path or the entire first line of the error WITHOUT stopping the rest of the script from running?
As requested, here's my earlier comments as an answer:
Get-ChildItem -Path $Path -Filter $Filename -File -Recurse -Force -ErrorVariable FailedItems -ErrorAction SilentlyContinue | ForEach-Object { Get-FileHash -Path $_.FullName | Select-Object * }
$FailedItems | Foreach-Object {$_.CategoryInfo.TargetName} | Out-File "C:\Users\sailingbikeruk\Desktop\noaccess.log"
I have added the -File parameter to Get-ChildItem, because you are specifically dealing with only files.
I also added the -ErrorVariable and -ErrorAction parameters to the Get-ChildItem command. -ErrorVariable FailedItems defines a custom name for a variable which stores errors from the command during processing. -ErrorAction SilentlyContinue, tells the script to continue without notifying you of the errors.
Once your command has finished processing, you can parse the content of the $FailedItems variable. In the example above, I've output the TargetName to a file so that you can read it at your leisure, (please remember to adjust its file path and name as needed, should you also wish to output it to a file).

Import-Csv and Export-Csv with same name and path

This should be a fairly simple question to answer, but I haven't been able to find a resource online. I need to remove rows from a csv with out changing the filename or path. Currently I am trying to import the file, filter the items I need and export that data to the same location with the same file name:
foreach ($csv in $csvLists){
Import-Csv $csv | Where-Object {[DateTime]$_.'Date' -ge $2WeeksOld} | Export-Csv $csv -NoType -Force
}
Also tried:
foreach ($csv in $csvLists){
Import-Csv $csv | Where-Object {[DateTime]$_.'Date' -ge $2WeeksOld} | Export-Csv $csv.PSParentPath -NoType -Force
}
I am getting access denied errors:
Export-Csv : Access to the path 'D:\Test' is denied.
At C:\script.ps1:20 char:71
+ ... .'Date' -ge $2WeeksOld} | Export-Csv $csv.PSParentPath -NoType -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Export-Csv], UnauthorizedAccessException
+ FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.ExportCsvCommand
What am I missing here? Is the issue that powershell is reading the file and cant export over it at the same time? Should I try setting the imported data as a variable and then exporting the csv outside the import-csv pipeline?
As always thanks for any help.
The problem here is that you're trying to do everything in one line, and consequently you're opening a file (which locks it) and trying to overwrite the same file (which needs to lock it) at the same time. That won't work.
Try:
foreach ($csv in $csvLists){
$Data = Import-Csv $csv | Where-Object {[DateTime]$_.'Date' -ge $2WeeksOld}
$Data | Export-Csv $csv -NoType -Force
}
Or, if $csvLists is an array of FileInfo objects (such as from the output of Get-ChildItem):
foreach ($csv in $csvLists){
$Data = Import-Csv $csv.FullName | Where-Object {[DateTime]$_.'Date' -ge $2WeeksOld}
$Data | Export-Csv $csv.FullName -NoType -Force
}
Use $csv.FullName. This contains the full path for a file/folder.
Here are a few common properties of System.IO.FileInfo:
Property Contents
BaseName MyFile
Directory* <returns System.IO.DirectoryInfo object of "C:\Path\To">
Name MyFile.txt
FullName C:\Path\To\MyFile.txt
PSParentPath Microsoft.PowerShell.Core\FileSystem::C:\Path\To
I'm not aware of a use case for .PSParentPath. Looking at the content, I assume it's used when you need to specify the PSProvider as well as the string value of the parent. Otherwise you could just use .Directory.Name
You may find the class definitions for FileInfo and DirectoryInfo useful. They are the objects returned by Get-ChildItem. The links list Methods and Properties; they do not list Code/Note/ScriptProperty types.
tip: run $csv | Get-Member to see members of this object, look at MemberType.
* For System.IO.DirectoryInfo objects, use Parent. Have got some different behaviour in Win10 and am sure an alias was instroduced so that Parent works for FileInfo or Directory works for DirectoryInfo.

How to avoid an UnathorizedAccessException when using Get-ChildItem?

I am using PowerShell 5.0 and working on a script to find and list all the versions of log4net.dll under the current directory recursively.
Get-ChildItem log4net.dll -Recurse | % versioninfo | Export-Csv "C:\MyJunk\log4net.csv"
The above statement begins returning version information as expected but execution stops at the first folder I lack permission to access:
Get-ChildItem : The specified network name is no longer available.
At line:1 char:1
+ Get-ChildItem log4net.dll -Recurse | % versioninfo | Export-Csv "C:\M ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ReadError: (J:\ArcPlan_OracleWallet\Production:String) [Get-ChildItem], IOException
+ FullyQualifiedErrorId : DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand
Get-ChildItem : Access is denied
At line:1 char:1
+ Get-ChildItem log4net.dll -Recurse | % versioninfo | Export-Csv "C:\M ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ChildItem], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetChildItemCommand
I am running Windows PowerShell ISE as Adminstrator. ExecutionPolicy is RemoteSigned and $ErrorActionPreference is Continue.
Ideally I would like the script to interrogate each folder's ACL and bypass all folders (and their contents) I lack permission to access. However another solution would one in which hard-coded folders are bypassed. Being a novice in PowerShell I focused on the later.
I have tried bypassing the first problem folder (by name) to see if I could get that working, but encounter the same exception and processing stops.
Get-ChildItem log4net.dll -Recurse | Where-Object { $_.FullName -notmatch '\\ArcPlan_OracleWallet\\?'} | export-csv 'C:\MyJunk\log4net.csv'
Thanks.
If you want to ignore the errors, use -ErrorAction SilentlyContinue.
There are other useful values to this parameter, as you can discover here and here.
Here is a nice question quite on-topic.
You can also fetch help about this with Get-Help about_CommonParameters.
(Hi and welcome, if you dig this answer, read this ^^).
I believe the issue was that the Get-ChildItem log4net.dll -Recurse would fail before the Where-Object could filter out the unwanted directories.
I want to avoid hard-coding directories, but here is my (klunky) solution so far.
## Version information will be retrieved for $fileName
$fileName = 'log4net.dll'
$ErrorActionPreference = 'Continue'
## Get directies - excluding those you lack permission to access
$directories = Get-ChildItem -Directory |
Where-Object {$_.FullName -inotmatch 'directory-1' -and
$_.FullName -inotmatch 'directory-2' -and
$_.FullName -inotmatch 'directory-3'
}
## Array to hold version information
$allFilesVersionInfo = #()
foreach ($directory in $directories) {
## Get all files recursively
$files = Get-ChildItem -Path $directory.FullName $fileName -Recurse
foreach ($file in $files) {
## Get version information and add to array
$fileVersionInfo = $file | % versioninfo
$allFilesVersionInfo += $fileVersionInfo
}
}
# Write version information in arra to file
$exportFullPath = "C:\MyJunk\$($fileName)-version.csv"
$allFilesVersionInfo | Export-Csv -Path $($exportFullPath)

Powershell - Export Multiple CSV's into unique folders

I have been working on a PowerShell script for the better part of well a week or two. I've been able to get some parts of it working however I'm unable to fully get this automated.
I deal with a lot of CSV files on a daily basis, I have been tasked with uploading them into our software however sometimes they're too large to handle so I break them down based upon their "type" (it's a column in the CSV) and I export it to a single CSV per "type". I've been able to accomplish this with the following:
$file = gci -Filter "*.csv";
Import-Csv $file `
| Group-Object –Property “type” `
| Foreach-Object `
{
$path=$_.name+”.csv” ; $_.group `
| Export-Csv –Path $path –NoTypeInformation
}
So this works wonderfully, for each individual CSV. Unfortunately I don't have the time to do this for each individual CSV. Now I come to my other PowerShell script:
get-childitem -Filter "*.csv" `
| select-object basename `
| foreach-object{ $path=$_.basename+".csv" #iterate through files.
if(!(Test-Path -path $_.basename)) #If the folder of the file can't be found then it will attempt to create it.
{
New-Item $_.basename -type directory; $file=$_.basename+".csv";
Import-Csv $file `
| Group-Object -Property "Type" `
| Foreach-Object {
$path=$_.name+".csv"; $_.group `
| `
if(!(Test-Path -path $path2))
{
New-Item $path2 -type directory
Export-Csv -Path $path2 + "\" + $path -NoTypeInformation
}
else
{
"Failed on: " + $_.basename
#Export-Csv -Path $_.basename + "\" + $path -NoTypeInformation
}
}
}
else
{
Import-Csv $path `
| Group-Object -Property "Type" `
| Foreach-Object {$path=$_.basename+".csv" ; $_.group
if(Test-Path -path $._)
{
New-Item $path2 -type directory
Export-Csv -Path $path2 + "\" + $path -NoTypeInformation
}
#else
#{
Write-Host "Failed on: $_.basename"
#Export-Csv -Path $_.basename + "\" + $path -NoTypeInformation
#}
}
}
}
I just can't wrap my head around "why" this isn't working effectively. I have two conditionals. Is there a folder for the CSV? If no create one. I have to have another one because one of the "types" contains a \ which errors out if I don't have the folder, so I automatically try to create it. When I run the script I get the Path is null.
The Error is:
The term ' ' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or
if a path was included, verify that the path is correct and try again.
At C:\Users\c.burkinshaw\foldermake.ps1:11 char:26
+ | ` <<<<
+ CategoryInfo : ObjectNotFound: ( :String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Test-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\c.burkinshaw\foldermake.ps1:12 char:45
+ if(!(Test-Path -path <<<< $path2))
+ CategoryInfo : InvalidData: (:) [Test-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand
Any help would be greatly appreciated, if you have questions please don't hesitate to ask.
You have not defined $path2 anywhere, so something like test-path -path $path2 will say path is null. And in one place you are using $._ which will again give errors.
Edit after question updated with error message:
Your error message also says the same
Test-Path : Cannot bind argument to parameter 'Path' because it is
null. At C:\Users\c.burkinshaw\foldermake.ps1:12 char:45
+ if(!(Test-Path -path <<<< $path2))
Also the other error is in:
$path=$_.name+".csv"; $_.group `
| `
what are you trying to do here with the $_.group?
It is not proper. You cannot do $_.group | and provide some if statement.
Other comments:
Why are using $_.basename and then appending .csv? You could have just used $_.name. Try to not use the select-object basename - I don't see the value.
Extract the common import-csv and export-csv part into a function.