Powershell Create-item creates multiple folders on list - powershell

Location.csv Sample I'm trying to create a single folder by prompting the user for userID and the group used to map the home folder.
The below script creates the user's home folder in each location and not in the location matching the prompt.
Running the script without the foreach loop doesn't work.
$Locations = Import-Csv "C:\Scripts\CreateHomeFolder\Location.csv"
$UserName = Read-Host "Enter User Logon Name"
$UserGroup = Read-Host "Enter User's Home drive group"
foreach($Location in $Locations.Location){
if ($Locations.Groups -eq $UserGroup){
New-Item -Name $UserID -Path $Locations.Location -ItemType Directory -Verbose
}
}
what am I doing wrong here?

As commented, you have the variables wrong. The iterating variable is $location and in each iteration it is an object taken from the array $locations.
Use
foreach($Location in $Locations){
if ($Location.Groups -eq $UserGroup){
New-Item -Name $UserID -Path $Location.Location -ItemType Directory -Verbose
}
}

went back to the drawing board, refined my google search and found the solution to be:
$Locations = Import-Csv "C:\Scripts\CreateHomeFolder\Locations.csv" | Where-Object Groups -eq $UserGroup | select Location
New-Item -Name $UserName -Path $Locations.Location -ItemType Directory -Verbose

Theo's helpful answer addresses your immediate problem.
Your own answer improves on your original approach, though by not using
select -ExpandProperty Location - note the use of -ExpandProperty (select is the built-in alias for the Select-Object cmdlet) - it creates unnecessary duplication (and processing overhead) by having to refer to Location twice.
Another option is to use direct property access:
New-Item -Name $UserName -ItemType Directory -Verbose -Path (
(
Import-Csv C:\Scripts\CreateHomeFolder\Locations.csv |
Where-Object Groups -eq $UserGroup
).Location
)

Related

Get location of specific SCCM device collection in Powershell

I am writing a script to export the names of all computer in a device collection to a txt file. My script works as expected but I would like to preserve the folder structure in the exported file structure. For this I need to get the location of the Device Collection.
My Question:
Is there a way to get the location of a SCCM Device Collection in PowerShell?
I've stumbled across a few posts like this and this that use WMI and WQL for this, but I wasn't able to get those working in my script and I would like to do everything in PowerShell whenever possible.
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
$totalCollections = $collections.length
"Number of Collections: $totalCollections"
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
$i++
"Writing File $i of $totalCollections"
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
$file = New-Item -Path "C:\Temp\exporte\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
}
}
I would like to expand this code so that the txt files are placed in the corresponding subfolder analog to the SCCM file structure. E.g rootFolder/rooms/
I was using this module until now but wasn't able to find anything that gives me back the specific location of a collection.
Thanks in advance
I wasn't able to find a way to do this in plain PowerShell and the SCCM Module. In the end I did it like #FoxDeploy suggested. I made a SQL query select for each collection (performance isn't an issue in my case) on our SCCM database to get the folder path. I then used this to place the export file in the appropriate place.
This is my working example with some confidential lines removed
## Parameter ##
$exportLocation = [removed]
$sqlServer = [removed]
$db = [removed]
$query = "SELECT [ObjectPath] FROM [removed].[v_Collections] WHERE CollectionName ="
$SiteCode = [removed] # Site code
$ProviderMachineName = [removed] # SMS Provider machine name
# Customizations
$initParams = #{}
# Import the ConfigurationManager.psd1 module
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module [removed]\..\ConfigurationManager.psd1" #initParams
}
# Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName #initParams
}
Set-Location "$($SiteCode):\" #initParams
# get all collections and save them to an array
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
# total number of collections
$totalCollections = $collections.length
# output to console
"Number of Collections: $totalCollections"
# empty output directory
Set-Location [removed]
Remove-Item $exportLocation\* -Recurse -Force
Set-Location [removed]
# loop through all collections
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
# print progress
$i++
"Writing File $i of $totalCollections"
# remove all characters, that aren't compatible with the windows file naming scheme (/\:*?"<>|)
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
# get members of collection
$collectionMembers = (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
# write to file
Set-Location [removed]
$path = (Invoke-Sqlcmd -ServerInstance $sqlServer -Database $db -Query "$query '$collection'").Item("ObjectPath")
New-Item -ItemType Directory -Force -Path "$exportLocation$path"
$file = New-Item -Path "$exportLocation$path\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value $collectionMembers
Set-Location [removed]
}
}
hope this helps someone. Thanks #FoxDeploy

When using Get-ADUser -Filter {}, process is slow

I am trying to clear out some orphaned user shares on a DFS share. I want to compare the full name of the folder to the HomeDirectory property of a specified object using Get-ADUser -Filter.
If I use for instance (Get-ADUser $varibale -Properties * | Select Homedirectory) I get an error displayed when the account is not found. So I used -Filter to hide the error if there is no account found. However, this is much slower than the -Properties * | Select method.
Script:
$path = Read-Host -Prompt "Share path...."
$Dirs = Get-ChildItem $Path
foreach ($D in $Dirs) {
$Login = Get-ADUser -Filter {HomeDirectory -eq $d.FullName}
if ($d.FullName -ne $Login."HomeDirectory") {
$host.UI.RawUI.WindowTitle = "Checking $d..."
$choice = ""
Write-Host "Comparing $($d.FullName)......." -ForegroundColor Yellow
$prompt = Write-Host "An account with matching Home Directory to $($d.FullName) could not be found. Purge $($d.fullname)?" -ForegroundColor Red
$choice = Read-Host -Prompt $prompt
if ($choice -eq "y") {
function Remove-PathToLongDirectory {
Param([string]$directory)
# create a temporary (empty) directory
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$tempDirectory = New-Item -ItemType Directory -Path (Join-Path $parent $name)
robocopy /MIR $tempDirectory.FullName $directory
Remove-Item $directory -Force
Remove-Item $tempDirectory -Force
}
# Start of Script to delete folders from User Input and Confirms
# the specified folder deletion
Remove-PathToLongDirectory $d.FullName
}
} else {
Write-Host "Done!" -ForegroundColor Cyan
}
Write-Host "Done!" -ForegroundColor Cyan
}
You have a couple of suboptimal things in your code (like (re-)defining a function inside a loop, or creating and deleting an empty directory over and over), but your biggest bottleneck is probably that you do an AD query for each directory. You should be able to speed up your code considerably by making a single AD query and storing its result in an array:
$qry = '(&(objectclass=user)(objectcategory=user)(homeDirectory=*)'
$homes = Get-ADUser -LDAPFilter $qry -Properties HomeDirectory |
Select-Object -Expand HomeDirectory
so that you can check if there is an account with a given home directory like this:
foreach ($d in $dirs) {
if ($homes -notcontains $d.FullName) {
# ... prompt for deletion ...
} else {
# ...
}
}
Performance-wise I didn't notice a difference between the robocopy /mir approach and Remove-Item when deleting a 2 GB test folder with each command. However, if you have paths whose length exceeds 260 characters you should probably stick with robocopy, because Remove-Item can't handle those. I would recommend adding a comment explaining what you're using the command for, though, because the next person reading your script is probably as confused about it as I was.
$empty = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid())
New-Item -Type Directory -Path $empty | Out-Null
foreach ($d in $dirs) {
if ($homes -notcontains $d.FullName) {
# ... prompt for confirmation ...
if ($choice -eq 'y') {
# "mirror" an empty directory into the orphaned home directory to
# delete its content. This is used b/c regular PowerShell cmdlets
# can't handle paths longer than 260 characters.
robocopy $empty $d.FullName /mir
Remove-Item $d -Recurse -Force
}
} else {
# ...
}
}
Remove-Item $empty -Force
There's also a PowerShell module built on top of the AlphaFS library that supposedly can handle long paths. I haven't used it myself, but it might be worth a try.

powershell create folder in other userprofile with administrative privilege

i have a user with standard right, and i need to run a powershell script with admin right to do something, and finnaly create a folder and copy a single file in current logged userprofile.
How i can do this?
example:
C:\Users\USERNAME\FOO\FOO.TXT
i do this but, obviusly, create the folder in my admin profile
# DO SOMETHINGS BEFORE
$directory = $env:USERPROFILE + 'FOO'
if(!(Test-Path -Path $directory)){
New-Item -Path $env:USERPROFILE -Name "FOO" -ItemType "directory"
}
Copy-Item "testo.txt" -Destination $directory
# Copy-Item "arDigiCore.ini" -Destination $arDigiSign
Thanks in advance
EDIT:
1 - i run my powershell script, logged like standard user (e.g. user1), like a admin (e.g. admin1).
2 - the script install a program, and before end, check and in case create a folder in the path C:\Users\users1\foo
NB: I do not know before the name of the user logged in to execute the program
You can use query.exe to pull the current users. Then filter the active user that isn't you.
$user = (((& query user) | ? {$_ -like "*active*" -and $_ -notlike "AdminUserName"}).trim() -Replace '\s+',' ' -Split '\s')[0]
#Credit to Jaap Brasser https://gallery.technet.microsoft.com/scriptcenter/Get-LoggedOnUser-Gathers-7cbe93ea
then convert to a SID and match to the SID returned from Win32_UserProfile
$NTUser = New-Object System.Security.Principal.NTAccount($user)
$SID = ($NTUser.Translate([System.Security.Principal.SecurityIdentifier])).value
$directory = (gcim Win32_UserProfile | ? {$_.sid -eq $SID}).localpath
if(!(Test-Path -Path (Join-Path $directory FOO))){
New-Item -Path $Directory -Name "FOO" -ItemType "directory"
}
Copy-Item "testo.txt" -Destination (Join-Path $directory FOO)

powershell - copy specific user folders based on last modified

I need to copy the Documents, Favorites, and Desktop folders for each user that has logged in to the PC in the last 30 days.
I'm just starting to learn powershell and I've got a decent start I think, but I'm wasting too much time. This is a project for work, and I've found myself just digging for solutions to X problem only to run into another at the next turn. Spent about a month trying to get this sorted out thus far, and thrown away a lot of codes.
What I have as of right now is this:
Get-ChildItem -path c:\users |Where-Object { $_.lastwritetime -gt (get-date).AddDays(-30)}
I know that this line will return the user folders that I need. At this point, I need code that will go in to each childitem from above and pull out the Documents, Favorites, and Desktop folder.
Now the tricky part. I need the code to create a folder on c: with the username it is pulling those folders from.
So the solution should:
for each user logged in in last 30 days;
copy Documents, Favorites, Desktop folder from their user drive
create a folder on c:\ for that user name
paste Documents, Favorites, Desktop to that folder
To better cover the scope:
I have to reimage PCs a lot in my department. The process of "inventorying" a PC is copying those folders and replacing them on the new PC I image for the user. That way their desktop etc looks the same and functions the same when they get their new PC. This code will be part of a larger code that ultimately "inventories" the entire PC for me... Ultimately, I want to be able to run my script for 2 seconds and then pull X folders and X documents off the c: drive on that PC as opposed to click, click, click, click a hundred times for 9 users that have used the PC in the last 30 days.
Any ideas?
2dubs
$usersFoldr = Get-ChildItem -path c:\users | Where-Object { $_.lastwritetime -gt (get-date).AddDays(-30)}
foreach ($f in $usersFoldr){
$toFld = "c:usrTest\" + $f.Name +"\Desktop\"
New-Item $toFld -type directory -force
Get-ChildItem ($f.FullName + "\Desktop") | Copy-Item -destination $toFld -Recurse -Force
}
Thanks to #bagger for his contribution. He was close.
After some experimentation, I found that this is the actual solution:
$usersFoldr = Get-ChildItem -path c:\users | Where-Object {
$_.lastwritetime -gt (get-date).AddDays(-30)}
foreach ($f in $usersFoldr)
{
$doc = "c:\users\$f\documents"
$toFldDoc = "c:\$f\documents"
New-Item $doc -type directory -force
Copy-Item $doc $toFldDoc -recurse -Force
}
foreach ($f in $usersFoldr){
$desk = "c:\users\$f\desktop"
$toFldDesk = "c:\$f\desktop"
New-Item $desk -type directory -force
Copy-Item $desk $toFldDesk -recurse -Force
}
foreach ($f in $usersFoldr){
$fav = "c:\users\$f\favorites"
$toFldFav = "c:\$f\favorites"
New-Item $fav -type directory -force
Copy-Item $fav $toFldFav -recurse -Force
}
Then save this file, send a shortcut of it to the desktop, then change the target of the shortcut to this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -f "C:\YOURDIRECTORY\YOURSCRIPTNAME.ps1"
Then run that shortcut as an administrator. Works like gold.
Thanks for your help, guys! :)
For anyone interested in the whole script:
Inventory script to copy pertinent files for all users in last 30 days, gather printer hostname/driver/IP, gather serialnumber, gather make/model.
$usersFoldr = Get-ChildItem -path c:\users | Where-Object {
$_.lastwritetime -gt (get-date).AddDays(-30)}
foreach ($f in $usersFoldr){
$doc = "c:\users\$f\documents"
$toFldDoc = "c:\inventory\$f\documents"
New-Item $doc -type directory -force
Copy-Item $doc $toFldDoc -recurse -Force
}
foreach ($f in $usersFoldr){
$desk = "c:\users\$f\desktop"
$toFldDesk = "c:\inventory\$f\desktop"
New-Item $desk -type directory -force
Copy-Item $desk $toFldDesk -recurse -Force
}
foreach ($f in $usersFoldr){
$fav = "c:\users\$f\favorites"
$toFldFav = "c:\inventory\$f\favorites"
New-Item $fav -type directory -force
Copy-Item $fav $toFldFav -recurse -Force
}
Get-WMIObject -class Win32_Printer | Select Name,DriverName,PortName
|Export-CSV -path 'C:\Inventory\printers.csv'
Get-WmiObject win32_bios |foreach-object {$_.serialnumber} |out-file
'c:\Inventory\SerialNumber.txt'
Get-WmiObject Win32_ComputerSystem | Select Model,Manufacturer |out-file
'c:\Inventory\MakeModel.txt'
Again, save this file, send a shortcut of it to the desktop, then change the target of the shortcut to this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -f "C:\YOURDIRECTORY\YOURSCRIPTNAME.ps1"
You can also retrieve a list of installed software by adding this line to the script:
get-wmiobject win32_product | select Name |export-csv -path 'c:\inventory
\software.csv'

Delete multiple files or folders from a CSV file that contain more than one columns (Powershell)

I need some help with script that will delete AD Disabled users Home Folders and Roaming Profiles folders on the Server (DC).
Steps That I already done, I create a powershell command:
Import-Module ActiveDirectory
Get-ADUser -SearchBase "OU=Marked for Deletion,OU=Disable Users,DC=******,DC=com" -Filter * -Property * |
Select-Object -Property homeDirectory,profilePath | Export-CSV -Path .\Remove.csv
This Command export the properties of home folders and roaming profile folders of disabled users.
Now' the CSV file contains two colmuns, one is "homeDirectory" and second "profilePath"
The Problem is, when i execute this script, i get error.
$folders = Get-Content "C:\lab\remove.csv"
foreach ($homeDirectory in $folders) {
Remove-Item -Path $homeDirectory -force -Recurse
}
foreach ($profilePath in $folders) {
Remove-Item -Path $profilePath -force -Recurse
}
write-host -foregroundcolor yellow "Delete action complete"
Can somebody help me with this, I will appreciate it.
First I would remove the type information from your CSV like so:
Import-Module ActiveDirectory
Get-ADUser -SearchBase "OU=Marked for Deletion,OU=Disable Users,DC=******,DC=com" -Filter * -Property * |
Select-Object -Property homeDirectory,profilePath |
Export-CSV -Path .\Remove.csv -NoTypeInformation
Then for your delete code I would use this:
Import-Csv "C:\lab\remove.csv" | % {
Remove-Item -Path $_.homeDirectory -force -Recurse
Remove-Item -Path $_.profilePath -force -Recurse
}
write-host -foregroundcolor yellow "Delete action complete"
The problem with your code is that you are not looping through a column, you looping by line and then doing it twice. To do it your way you would need to split the line at the comma.