Get location of specific SCCM device collection in Powershell - 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

Related

Reference current drive path in a Foreach loop through drives

I'm trying to find and log any local PST files on multiple machines, accross all local drives on each machine. I have the code below so far, but I can't get it to run in the context of the root of the current drive in the foreach loop, it just runs in the context of where the script was run from.
If (-not (Test-Path -Path "\\BETH-FS01\F$\PSTBackups")){
Exit 1
} # check if PC is connected to domain, some laptops aren't always on the VPN
Else{
#defines the path where to store the log CSV
$LogPath = "\\BETH-FS01\F$\PSTBackups"
$Log = #() #object array to store "PST objects"
# defining string variable to combine with another string variable later on
$Ext = ".pst"
# creates array of local drives on PC
$Drives = Get-PSDrive -PSProvider 'FileSystem'
foreach ($Drive in $Drives) {
# searches drive for PST files, creates an array of those files,
# then passes each through to create PST objects
$Log = ForEach ($PST in ($PSTS = Get-ChildItem -LiteralPath $Drive.Name -Include *.pst -Recurse -Force -erroraction silentlycontinue)){
New-Object PSObject -Property #{
ComputerName = $env:COMPUTERNAME
Path = $PST.DirectoryName
FileName = $PST.BaseName+$Ext
Size = "{0:N2} MB" -f ($PST.Length / 1mb)
Date = $PST.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
} #creates PST object
}
}
}
$Name = $env:COMPUTERNAME #define string to use in log path below
$Log | Export-Csv $LogPath\$Name.csv -NoTypeInformation #exports the Log object array to a CSV
To clarify, I'm trying to find out how to reference the fact that if the foreach loop is currently doing the C: drive, it would use the "C:" path as the -path for Get-ChildItem, i.e.:
$PSTS = Get-ChildItem -path "*somehow reference C: drive path*" -Include *.pst -Recurse -Force -erroraction silentlycontinue
Sorry if the code is sloppy, I'm not the best at keeping clean code...
In this line...
$Log = ForEach ($PST in ($PSTS = Get-ChildItem -LiteralPath $Drive.Name -Include *.pst -Recurse -Force -erroraction silentlycontinue)){
... you are just passing the drive letter for -LiteralPath, which is not a path.
You need to pass the root path, e. g. "C:\", not just "C" or "C:". The latter only means the current directory on drive C.
This should do the trick:
$Log = ForEach ($PST in ($PSTS = Get-ChildItem -LiteralPath $Drive.Root -Include *.pst -Recurse -Force -erroraction silentlycontinue)){

PowerShell find files by extension on multiple servers and export

I wrote a script to find the particular file on windows servers based on Disks and exporting that list to .txt file for each server and drive.
I want to run the script for multiple servers and export all the list details in one csv or excel file with server name and the file location path as output.
#Clearing the Host file
Clear-Host
#Get all the list of Servers
$Machines = get-content "C:\Scripts\Servers.txt"
#Get all the list of Disks to search
$Disks = get-content "C:\Scripts\Disks.txt"
#Lopping through specified servers
foreach ($Machine in $Machines)
{
#Lopping through each Disks
foreach ($Disk in $Disks)
{
if (Test-Path \\$Machine\$Disk$)
{
Write-Host Checking $Machine Disk $Disk -BackgroundColor DarkRed
Get-ChildItem -Path \\$Machine\$Disk$\ -Filter log4j.jar -Recurse -Name -Force | Out-File "C:\Scripts\Output\$Machine $Disk.txt"
}
}
}
Thanks in Advance
Don't send output from the script until you have the information you need.
Using a custom object will allow you to store the values you want
#Clear Host
Clear-Host
#Get all the list of Servers
$Machines = get-content "C:\Scripts\Servers.txt"
#Get all the list of Disks to search
$Disks = #('D','G')
# Looping through specified servers
foreach ($Machine in $Machines) {
#Looping through each Disks
$FileResults = $Disks | ForEach-Object {
If (Test-Path \\$Machine\$_$) {
Write-Host Checking $Machine Disk $_ -BackgroundColor DarkRed
$DiskResults = Get-ChildItem -Path \\$Machine\$_$\ -Filter log4j.jar -Name -Recurse -Force
#
# Send the results of each disk to custom object, to be stored in $FileResults
#
[pscustomobject]#{Disk=$_;Files=$DiskResults}
}
}
If (-not $FileResults) {
$FileResults = 'None found'
}
#
# Send the results of each completed server
#
[pscustomobject]#{ComputerName=$Machine;Found=$FileResults}
#
}

Issues with Copy-item script

I have a script that needs to copy a list of files to ceraitn directories and locations on target servers.
I was able to understand that I need to creat an csv file as follows:
I need to understand from you how to make the files from source location to their adjacent file in target location. Any ideas?
My code looks like this:
# customize log file
$date = (Get-Date -Format d).ToString() | foreach {$_ -replace "/", "_"}
$time = (Get-Date)
$scriptDir = "D:\Scripts\ServerBuildToolkitT1\SingleFileUpfate\"
$logDir = "D:\Scripts\ServerBuildToolkitT1\Logs\SingleFileUpfate\"
$logFileName = "SingleFileUpfate $date.log"
$sources = #()
$destinsyions = #()
function CSV {
Import-Csv D:\Scripts_PS\SD.csv | ForEach-Object {
$sources += $_."Source Location"
$destinations += $_."Destination Location"
}
}
# this file contains the list of destination server that you want copy
# file/folder to
$computers = Get-Content "D:\Scripts_PS\ServerList.txt"
function main
{
foreach ($computer in $computers) {
foreach ($destination in $destinations) {
Write-Output "$([DateTime]::Now) Copying files update to $computer now" |
Out-File -FilePath "$logDir\$logFileName" -Append
Copy-Item $sources -Destination "\\$computer\$destination" -Force -Recurse -Verbose:$false
}
}
}
csv
main
Write-Output "$([DateTime]::Now) the operation SingleFileUpdate completed successefully" |
Out-File -FilePath "$logDir\$logFileName" -Append
i have updtaed the Script (as seen above) and now i am getting the following ERROR
"WARNING: One or more headers were not specified. Default names starting with "H" have been used in place of any missing headers."

Powershell search path with wildcard-string-wildcard

I am trying to search a log file for updates that were not installed, then using the returned array install the updates. Problem is my files are named:
Windows6.1-KB3102429-v2-x64.msu
My parsed array has a item of KB3102429 how can I use a wild card - call the array item - then another wildcard .msu
my code is listed below:
# Read KBLIST.txt and create array of KB Updates that were not installed
$Failed = Get-Content -Path C:/Updates/KBLIST.txt | Where-Object {$_ -like '*NOT*'}
# create a list of all items in Updates folder
$dir = (Get-Item -Path "C:\Updates" -Verbose).FullName
# Parse the $Failed array down to just the KB#######
for($i = $Failed.GetLowerBound(0); $i -le $Failed.GetUpperBound(0); $i++)
{
$Failed[$i][1..9] -join ""
# Search the $dir list for files that contain KB####### and end in .msu then quiet install
Foreach($item in (ls $dir *$Failed[$i]*.msu -Name))
{
echo $item
$item = "C:\Updates\" + $item
wusa $item /quiet /norestart | Out-Null
}
}
It works down to the Foreach($item in (ls $dir *$Failed[$i]*.msu -Name)).
If I just use * instead of the wildcard,string,wildcard it returns a list of all the .msu files for the basic syntax it correct.
It was hard to follow your work since you used aliases, but I think this should be able to accomplish what you're looking for.
$UpdateFolder = 'C:\Updates'
$FailedUpdates = Get-Content -Path C:/Updates/KBLIST.txt | Where-Object {$_ -like '*NOT*'}
foreach ( $Update in $FailedUpdates )
{
Write-Host -Object "Update $Update failed"
$UpdatePath = Get-Item -Path "$UpdateFolder\*$Update*.msu" | Select-Object -ExpandProperty FullName
Write-Host -Object "`tReinstalling from path: $UpdatePath"
wusa $UpdatePath /quiet /norestart | Out-Null
}

Delete old files in recycle bin with powershell

Ok, I have a script I am writing in powershell that will delete old files in the recycle bin. I want it to delete all files from the recycle bin that were deleted more than 2 days ago. I have done lots of research on this and have not found a suitable answer.
This is what I have so far(found the script online, i don't know much powershell):
$Path = 'C' + ':\$Recycle.Bin'
Get-ChildItem $Path -Force -Recurse -ErrorAction SilentlyContinue |
#Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-3) } |
Remove-Item -Recurse -exclude *.ini -ErrorAction SilentlyContinue
It is working great with one exception, it checks the file parameter "LastWriteTime". That is awesome if the user deletes the file they same day they modify it. Otherwise it fails.
How can I modify this code so that it will check when the file was deleted, not when it was written.
-On a side note, if I run this script from an administrator account on Microsoft Server 2008 will it work for all users recycle bins or just mine?
Answer:
the code that worked for me is:
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e",""
$dtDeletedDate = get-date $DeletedDate
If($dtDeletedDate -lt (Get-Date).AddDays(-3))
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
}#EndIF
}#EndForeach item
It works awesome for me, however 2 questions remain...How do I do this with multiple drives? and Will this apply to all users or just me?
WMF 5 includes the new "Clear-RecycleBin" cmdlet.
PS > Clear-RecycleBin -DriveLetter C:\
These two lines will empty all the files recycle bin:
$Recycler = (New-Object -ComObject Shell.Application).NameSpace(0xa)
$Recycler.items() | foreach { rm $_.path -force -recurse }
This article has answers to all your questions
http://baldwin-ps.blogspot.be/2013/07/empty-recycle-bin-with-retention-time.html
Code for posterity:
# -----------------------------------------------------------------------
#
# Author : Baldwin D.
# Description : Empty Recycle Bin with Retention (Logoff Script)
#
# -----------------------------------------------------------------------
$Global:Collection = #()
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
$csvfile = "\\YourNetworkShare\RecycleBin.txt"
$LogFailed = "\\YourNetworkShare\RecycleBinFailed.txt"
function Get-recyclebin
{
[CmdletBinding()]
Param
(
$RetentionTime = "7",
[Switch]$DeleteItems
)
$User = $env:USERNAME
$Computer = $env:COMPUTERNAME
$DateRun = Get-Date
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e","" #Invisible Unicode Characters
$DeletedDate_datetime = get-date $DeletedDate
[Int]$DeletedDays = (New-TimeSpan -Start $DeletedDate_datetime -End $(Get-Date)).Days
If($DeletedDays -ge $RetentionTime)
{
$Size = $Recycler.GetDetailsOf($item,3)
$SizeArray = $Size -split " "
$Decimal = $SizeArray[0] -replace ",","."
If ($SizeArray[1] -contains "bytes") { $Size = [int]$Decimal /1024 }
If ($SizeArray[1] -contains "KB") { $Size = [int]$Decimal }
If ($SizeArray[1] -contains "MB") { $Size = [int]$Decimal * 1024 }
If ($SizeArray[1] -contains "GB") { $Size = [int]$Decimal *1024 *1024 }
$Object = New-Object Psobject -Property #{
Computer = $computer
User = $User
DateRun = $DateRun
Name = $item.Name
Type = $item.Type
SizeKb = $Size
Path = $item.path
"Deleted Date" = $DeletedDate_datetime
"Deleted Days" = $DeletedDays }
$Object
If ($DeleteItems)
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
if ($?)
{
$Global:Collection += #($object)
}
else
{
Add-Content -Path $LogFailed -Value $error[0]
}
}#EndIf $DeleteItems
}#EndIf($DeletedDays -ge $RetentionTime)
}#EndForeach item
}#EndFunction
Get-recyclebin -RetentionTime 7 #-DeleteItems #Remove the comment if you wish to actually delete the content
if (#($collection).count -gt "0")
{
$Collection = $Collection | Select-Object "Computer","User","DateRun","Name","Type","Path","SizeKb","Deleted Days","Deleted Date"
$CsvData = $Collection | ConvertTo-Csv -NoTypeInformation
$Null, $Data = $CsvData
Add-Content -Path $csvfile -Value $Data
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)
#ScriptEnd
Had to do a bit of research on this myself, the recycle bin contains two files for every file deleted on every drive in win 10 (in win 7 files are as is so this script is too much and needs to be cut down, especially for powershell 2.0, win 8 untested), an info file created at time of deletion $I (perfect for ascertaining the date of deletion) and the original file $R, i found the com object method would ignore more files than i liked but on the up side had info i was interested in about the original file deleted, so after a bit of exploring i found a simple get-content of the info files included the original file location, after cleaning it up with a bit of regex and came up with this:
# Refresh Desktop Ability
$definition = #'
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
public static void Refresh() {
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
}
'#
Add-Type -MemberDefinition $definition -Namespace WinAPI -Name Explorer
# Set Safe within deleted days and get physical drive letters
$ignoreDeletedWithinDays = 2
$drives = (gwmi -Class Win32_LogicalDisk | ? {$_.drivetype -eq 3}).deviceid
# Process discovered drives
$drives | % {$drive = $_
gci -Path ($drive+'\$Recycle.Bin\*\$I*') -Recurse -Force | ? {($_.LastWriteTime -lt [datetime]::Now.AddDays(-$ignoreDeletedWithinDays)) -and ($_.name -like "`$*.*")} | % {
# Just a few calcs
$infoFile = $_
$originalFile = gi ($drive+"\`$Recycle.Bin\*\`$R$($infoFile.Name.Substring(2))") -Force
$originalLocation = [regex]::match([string](gc $infoFile.FullName -Force -Encoding Unicode),($drive+'[^<>:"/|?*]+\.[\w\-_\+]+')).Value
$deletedDate = $infoFile.LastWriteTime
$sid = $infoFile.FullName.split('\') | ? {$_ -like "S-1-5*"}
$user = try{(gpv "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\$($sid)" -Name ProfileImagePath).replace("$(gpv 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList' -Name ProfilesDirectory)\",'')}catch{$Sid}
#' Various info
$originalLocation
$deletedDate
$user
$sid
$infoFile.Fullname
((gi $infoFile -force).length / 1mb).ToString('0.00MB')
$originalFile.fullname
((gi $originalFile -force).length / 1mb).ToString('0.00MB')
""
# Blow it all Away
#ri $InfoFile -Recurse -Force -Confirm:$false -WhatIf
#ri $OriginalFile -Recurse -Force -Confirm:$false- WhatIf
# remove comment before two lines above and the '-WhatIf' statement to delete files
}
}
# Refresh desktop icons
[WinAPI.Explorer]::Refresh()
This works well also as a script with the task scheduler.
Clear-RecycleBin -Force