first time stackoverflower.
I have a need to remove 'ghost' entries from the PackageRegistrations of my software deployment GPOs.
What I mean by that is that there are more entries in the ADSI object than there are MSI/MST files associated to the GPO. i.e. what this blogger also seems to be experiencing http://justanotheritblog.co.uk/2016/11/15/list-msi-paths-from-software-installation-policies/ (I just found this when looking into my issue).
When nosing around the properites in ADSI, I stubmbled across 'msiScriptName', which seems to have a value of either 'A' or 'R'.
What I cannot seem to find, is any information as to what these values may represent.
Any ideas on what the 'A' and 'R' mean and/or how to correctily identify and/or remove the 'ghost' entries greatly recieved.
The reason for this is that I have a whole bunch of software deployment GPOs that need the file path updating, and rather than manually editing each one I wanted to use PowerShell to bulk update them - we are moving to DFS from fixed file server, so I need to update the msiFileList properties. This I can do, but do not want to waste processing overhead on irrelevant objects.
The following is rough code suggesting how I am doing this
$MSIFiles = #()
# Get all the SoftwareDeployment GPOs, indicated by a displayname continaing 'Install' and create an object for each MSI/MST associated to it.
$Packages = Get-GPO -All | Where-Object { $_.DisplayName -like "*Install*" } | Get-ADObjectGPOPackages -Domain 'skyriver.internal'
foreach ($p in $Packages)
{
$msiCount = ($p.msiFileList | Measure-Object).Count
$msiFileListNew = #()
for ($i = 0; $i -lt $msiCount; $i ++)
{
$msiFile = $p.msiFileList[$i] -replace 'hoth(01|01.skyriver.internal|02.skyriver.internal)','skyriver.internal\data'
$msiFileListNew += $msiFile
}
$Properties = [ordered]#{
'gpoDisplayName' = $p.gpoDisplayName
'PackageNumber' = $p.PackageNumber
'DisplayName' = $p.DisplayName
'CN' = $p.CN
'DistinguishedName' = $p.DistinguishedName
'Identity' = $p.Identity
'msiFileList' = $msiFileListNew
}
$obj = New-Object -TypeName psobject -Property $Properties
$MSIFiles += $obj
}
# Now make the replacements.
foreach ($m in $MSIFiles)
{
Set-ADObject -Identity $m.Identity -Server dagobah.skyriver.internal -Replace #{msiFileList = $m.msiFileList}
}
So far as I can tell, A is Advertised (ie available for install), and R is Remove. The Ghost packages probably have an R as they are no longer valid and are therefore to be uninstalled (I'm not sure if this only applies if the "uninstall when it falls out of scope" option is enabled before deleting?).
Related
I have a script where I get all of the folders in d:\folder\*\*\ where the name is -like "*\Log". I then split the folder paths apart to run through wmi to get the corresponding services. After that I'm wanting to split apart the PathName property from $Services so I get everything before the \xxxxx.exe and add \log to the end of the result. Eventually I'll then use those paths to do some compression and archiving of files via a gci.
For whatever reason when I run the script below I the previous loops $LocalLogVar without "log" appended and the current loops LocalLogVar with log appended. I'm sure I'm doing something wrong that's blatantly obvious to somebody out there. If somebody could point me in the right direction on this it'd be much appreciated! I also apologize for the word vomit here, I've been looking at this script all day and my brain's pretty much used up.
A couple of notes:
The number of words in the paths vary which is why I can't manually do $LocalLogVar = "$Var1\$Var2\$Var3\Log"
If I don't have the [array] in front of $LogFolders the object type becomes a string and I get the previous loop's $LocalLogVar without "log" appended combined with the current loop's $LocalLogVar
I tried doing [collections.arraylist]$LogFolders=#() with no success
c:\folder is a shortcut to d:\folder, which is why there's c:\folder\xxx and d:\folder\xxx in the list below
SplitCount is -1 because I don't want the .exe from the path, I just want the folder structure
The naming convention for the string before .exe varies so I can't use an enumerated counter.
Example of first bullet:
word7-word8 #This is the previous loop's $LocalLogVar w/o "log" appended
C:\folder\word5\word6\word9-word8\log #This is the current loop's $LocalLogVar w/ "log" appended.
Example of the second bullet:
word7-word8C:\folder\word5\word6\word9-word8\log
What I should be getting:
D:\folder\word-anotherword\word7-word8\log
D:\folder\word-anotherword\word9-word8\log
C:\folder\word1\word7-word8\log
C:\folder\word1\word9-word8\log
C:\folder\word2\word7-word8\log
C:\folder\word2\word9-word8\log
D:\folder\word2\word10-word11\log
D:\folder\word2\word12-word8\log
C:\folder\word3\word7-word8\log
C:\folder\word3\word9-word8\log
D:\folder\word4\word7-word8\log
C:\folder\word4\word9-word8\log
C:\folder\word5\word6\word7-word8\log
C:\folder\word5\word6\word9-word8\log
C:\folder\word5\word6\word7-word8\log
C:\folder\word5\word6\word9-word8\log
$Folders = Get-ChildItem D:\folder\*\*\ -Directory -Recurse -Verbose `
| Where-Object { $_.fullname -like "*\Log" }
$2 = #()
$LogFolders = #()
foreach ($folder in $folders) {
$ServName = $folder.fullname.split('\')[2]
$ServType = $folder.fullname.split('\')[3]
$ServNameCheck = "*$($ServName.replace('-',' '))*"
$ServTypeCheck = "*$($ServType.replace('-',' '))*"
$PathName = Get-WmiObject -ClassName Win32_Service `
| Where-Object { $_.caption -like "$ServNameCheck" -and $_.caption -like "$ServTypeCheck" } `
| Select-Object Name, Caption, #{n = 'PathName'; e = { ($_.PathName).trim('"') } }
$2 += $PathName
}
$Services = $2 | Sort-Object pathname | Get-Unique -AsString
foreach ($ServPath in $services.pathname) {
$LocalLogVar = #()
if (Get-Variable `
| Where-Object { $_.name -match "^Split([0-9]|10)$" }) {
Get-Variable `
| Where-Object { $_.name -match "^Split([0-9]|10)$" } | Remove-Variable -Force
}
[int]$SplitCount = $ServPath.split('\').count
[int]$SplitCountCheck = $SplitCount - 1
$x = 0
do {
New-Variable -Name "Split$x" -Value "$($ServPath.split('\')[$x])"
$RegEx = "Split$x"
$LogFolderName = Get-Variable | Where-Object { $_.name -match $RegEx } | Select-Object value
[string]$LogFolders = $LogFolderName.value.ToString()
$LocalLogVar += $LogFolders + '\'
$x++
} until ($x -eq $SplitCountCheck)
$LocalLogVar = $LocalLogVar
$LocalLogVar = $LocalLogVar + "log"
[array]$LogFolders += $LocalLogVar
}
Wow, so that's a script. Kind of hard to follow, since some of it seems needlessly complex. I'm not sure if it will accomplish what you're looking for, but that's because you were super vague with your folder descriptions. Do the folders always start like this:
D:\folder<Service Short Name><Service Long Name>...\logs
If not you could be in trouble. The last four items on your example list of what you expect to see don't look like they are like that. I think the way your folders are laid out are like this:
D:\folder...<Service Short Name><Service Long Name>\logs
The difference being where the extra folders are located. If they're before the service like I think they are your script will miss things.
Now, on to getting your list that you want. What I see from looking at your script is that you get a folder list for all folders under D:\folder\*\*\ named 'log'. Then you split out the 3rd and 4th folders to get a service's short name, and long name respectively. Then one by one you pull a list of all services from WMI, and filter for just the service that matches the name and caption (short name, and long name) referred to by the folders. After that you make sure you only have one listing of any given service.
Regarding this first part of the script, you can make it faster by letting the file system provider filter things for you. Instead of pulling a folder list of everything and then filtering for paths that end in '\log', you should use the -filter parameter of the Get-ChildItem cmdlet like this:
$Folders = Get-ChildItem C:\temp\*\*\ -Directory -Recurse -Verbose -Filter 'log'
Then you should query WMI one time, save the result, then pick and choose from there based on your folders. Something like:
[array]$2 = foreach ($folder in $folders) {
$ServName,$ServType = $folder.fullname.split('\')[2,3] -replace '-',' '
$PathName = $AllServices |
Where-Object { $_.caption -like "*$ServName*" -and $_.caption -like "*$ServType*" } |
Select-Object Name, Caption, #{n = 'PathName'; e = { $_.PathName -replace '^(\w\S+) .*','$1' -replace '^([''"])([^\1]+)\1.*','$2' } }
}
$Services = $2 | Sort-Object pathname | Get-Unique -AsString
I did a little regex magic to clean up the pathname instead of just .trim('"') since this gets rid of parameters in the service execution, and cleans paths that are enclosed in single quotes not just double quotes. If what you have works for you feel free to keep it, but this is a little more capable. It may be worth noting that Get-Unique is case sensitive, so 'C:\folder\word3\word9-word8' and 'C:\folder\word3\word9-Word8' are different. You might want to do a .ToUpper() on your paths before you look for unique ones.
Once you have your array of services you loop through them, splitting the file path, reassembling it, and finally adding 'log' to the end of it. That was your way to remove the executable from the path. There's a cmdlet that was designed to do just that: split-path. Use that with Join-Path and that whole last loop gets much simpler:
[array]$LogFolders = foreach ($ServPath in $services.pathname) {
Join-Path (Split-Path $ServPath) 'log'
}
Lastly, try not to use +=, since PowerShell has to rebuild the whole array each time you do that. You'll notice I moved the $Variable = bit outside the loop in places that you do that.
I am trying to gather data from eventlogs of logons, disconnect, logoff etc... this data will be stored in a csv format.
This is the script i am working which got from Microsoft Technet and i have modified to meet my requirement. Script is working as it should be but there is looping going on which i can't figure out how it should be stopped.
$ServersToQuery = Get-Content "C:\Users\metho.HOME\Desktop\computernames.txt"
$cred = "home\Administrator"
$StartTime = "September 19, 2018"
#$Yesterday = (Get-Date) - (New-TimeSpan -Days 1)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = (Get-Date).AddDays(-1)
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server -Credential $cred
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "-"
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
#End
First time when i run the script, it runs fine and i get the csv output as it should be. When i run the script again than a new CSV is created (as it should be) but the same event log enteries are created twice and run it again than three enteries are created for the same event. This is very strange as a new csv is created each time and i dont not have -append switch for export-csv configured.
$FilteredOutput = #()
$Output = #()
I did try adding these two lines in above script as i read somewhere that it is needed if i am mixing multiple variables into a array (i do not understand this so applogies if i get this wrong).
Can someone please help me this, more importantly, I need to understand this as it is good to know for my future projects.
Thanks agian.
mEtho
It sounds like the$Output and $FilteredOutput variables aren't getting cleared when you run the script subsequent times (nothing in the current script looks to do that), so the results are just getting appended to these variables each time.
As you've already said, you could add these to the top of your script:
$FilteredOutput = #()
$Output = #()
This will initialise them as empty arrays at the beginning, which will ensure they start empty as well as make it possible for them to be appended to (which happens at the script via +=). Without doing this on the first run the script likely failed, so I assume you must have done this in your current session at some point for it to be working at all.
I am currently trying to automate license counting in Office 365 across multiple partner tenants using PowerShell.
My current code (aquired from the internet) with some modifications gives me this output:
Column A Column B Column C
-------- -------- --------
CustA LicA,LicB 1,3
CustB LicA,LicB 7,3
CustC LicA 4
But the output I want from this code is:
Column A Column B Column C
-------- -------- --------
CustA LicA 1
LicB 3
CustB LicA 7
LicB 3
Here is my current code which is exported using Export-Csv -NoType:
$tenantID = (Get-MsolPartnerContract).tenantid
foreach($i in $tenantID){
$tenantName = Get-MsolPartnerInformation -TenantId $i
$tenantLicense = Get-MsolSubscription -TenantId $i
$properties = [ordered]#{
'Company' = ($tenantName.PartnerCompanyName -join ',')
'License' = ($tenantLicense.SkuPartNumber -join ',')
'LicenseCount' = ($tenantLicense.TotalLicenses -join ',')
}
$obj = New-Object -TypeName psobject -Property $properties
Write-Output $obj
}
I have tried this along with several other iterations of code which all fail catastophically:
$properties = [ordered]#{
'Company' = ($tenantName.PartnerCompanyName -join ','),
#{'License' = ($tenantLicense.SkuPartNumber -join ',')},
#{'LicenseCount' = ($tenantLicense.TotalLicenses -join',')}
}
I was thinking about making a "sub-array" $tenantLicense.SkuPartnumber and $tenantLicense.TotalLicenses, but I'm not quite sure how to approach this with appending it to the object or "main-array".
A second loop for each $tenantLIcense should do the trick for you. I don't have access to an environment like yours so I cannot test this.
$tenantID | ForEach-Object{
$tenantName = Get-MsolPartnerInformation -TenantId $_
$tenantLicense = Get-MsolSubscription -TenantId $_
# Make an object for each $tenantLicense
$tenantLicense | ForEach-Object{
$properties = [ordered]#{
'Company' = $tenantName.PartnerCompanyName
'License' = $_.SkuPartNumber
'LicenseCount' = $_.TotalLicenses
}
# Send the new object down the pipe.
New-Object -TypeName psobject -Property $properties
}
}
Since you have multiple $tenantLicenses that have the same company name lets just loop over those and use the same company name in the output. Assuming this worked it would not have the same output as you desired since there no logic to omit company in subsequent rows. I would argue that is it better this way since you can sort the data now with out loss of data / understanding.
Notice I change foreach() to ForEach-Object. This makes it simpler to send object down the pipe.
Without providing the code solution I can say that you need to build up the array.
In terms of programming, you will need to iterate the array ARRAY1 and populate another one ARRAY2with the extra rows. For example if columns A,B are simple value and C is an array of 3 items, then you would add 3 rows in the new table with A,B,C1, A,B,C2 and A,B,C3. On each iteration of the loop you need to calculate all the permutations, for example in your case the ones generated by columnB and columnC.
This should be also possible with pipelining using the ForEach-Object cmdlet but that is more difficult and as you mentioned your relatively new relationship with powershell I would not pursuit this path, unless of coarse you want to learn.
I'm working on a deployment script for VMs on a VMWare platform but I'm stuck.
Basically, my script does this:
Receive information from Excel and first runs some check. It does that for each row, which will represent VM information, before any VM is created.
When all VMs are validated, the first VM will be created, and then the next one, etc.
One of my functions will calculate the best available storage disk. It returns the first storage disk with the most available diskspace.
That function looks like this:
Function Get-AvailableStorage {
$Threshold = "80" # GB
$TotalFreeSpace = Get-Cluster -Name Management |
Get-Datastore |
where Name -notlike "*local*" |
sort FreeSpaceGB -Descending
if ($SqlServices -notlike "NONE") {
$VMSize = 30 + $VMStorage + 10
} else {
$VMSize = 30 + $VMStorage
}
foreach ($StorageDisk in $TotalFreeSpace) {
[math]::floor($StorageDisk.FreeSpaceGB) | Set-Variable RoundedSpace
$FreeAfter = $RoundedSpace - $VMSize | sort -Descending
if ($FreeAfter -lt $Threshold) {
return $StoragePool = "VSAN"
} else {
return $StorageDisk.Name
}
}
}
The problem
When I have multiple VMs in my Excel, the storage disk is always the same, because the available diskspace is not being updated (because none of the VMs is being deployed yet).
I did some investigation on my own:
I have to figure out a way to update the column FreeSpaceGB but that is a ReadOnly property.
I then though to push every item in another array which I created myself but that also doesn't work. Still Readonly property.
Then I thought about using PSObject with an Add-Member, but I cannot get that working either (or I'm doing it wrong).
$ownarray= #()
$item = New-Object PSObject
$Global:TotalFreeSpaceManagement = Get-Cluster -Name Management |
Get-Datastore |
where Name -notlike "*local*" |
sort FreeSpaceGB -Descending
foreach ($StorageDisk in $Global:TotalFreeSpaceManagement) {
$item | Add-Member -Type NoteProperty -Name "$($StorageDisk.Name)" -Value "$($StorageDisk.FreeSpaceGB)"
$ownarray += $item
}
UPDATE
I use the hashtable like #Ansgar suggested. When I try it manually it's working perfectly, but in my script it's not.
When I have multiple VMs in an array, the previous datastore is being used and the space that is left is UPDATED.
Example:
VM1 is 120GB and uses VM-105. That disk has 299GB left.
VM2 is 130GB and uses VM-105. Then the disk has 289GB left.
Both VMs are getting suggested VM-105 based on the most free space.
VM-105 should have 299 - 130 = 160GB left is the script was working correct but somehow the $FreeSpace is updated, or $max is overwritten, and I cannot figure how this happens.
You need to keep track of the changes to the free space, while also maintaining the association between disk and the calculated remaining free space. To do that read your storage into a hashtable that associates disks with the available free space. Then use that hashtable for selecting a disk and updating the respective free space value when placing a VM.
$threshold = 80 # GB
# initialize global hashtable
$storage = #{}
Get-Cluster -Name Management | Get-Datastore | Where-Object {
$_.Name -notlike '*local*'
} | ForEach-Object {
$script:storage[$_.Name] = [Math]::Floor($_.FreeSpaceGB)
}
function Get-AvailableStorage([int]$VMSize) {
# find disk that currently has the most free space
$disk = ''
$max = 0
foreach ($key in $script:storage.Keys) {
$freespace = $script:storage[$key] # just to shorten condition below
if ($freespace -gt $max -and $threshold -lt ($freespace - $VMSize)) {
$max = $freespace
$disk = $key
}
}
# return storage and update global hashtable
if (-not $disk) {
return 'VSAN' # fallback if no suitable disk was found
} else {
$script:storage[$disk] -= $VMSize
return $disk
}
}
I am fairly new to PowerShell. I have created an exe that can be ran by a coworker that will go to 8 separate sql servers, to individual directories and check them for a list of created files. My current code checks for age less than one day and that it's not empty, however, I have been presented with a new problem. I need to be able to take a list in a text file/array/csv/etc and compare the list to the directory to ensure that there are not any additional files in the folder. All of the information is then formatted, exported to a file, and then emailed to certain recipients.
My question is, how do I create a script that works using this idea; preferably without considerable change to my current code as it is fairly length considering the sub directories of the 8 sql servers.. (current script redacted is as follows):
$today = get-date
$yesterday = $today.AddDays(-1)
$file_array = "XXX_backup*.trn", "XXX_backup*.bak", "XXY_backup*.trn", "XXY_backup*.bak"
$server_loc = \\hostname\e$\
$server_files = #(get-childitem $server_loc | where-object {$_.fullname -notmatch "(subdirectory)})
$server_complete_array = #("Files Directory ($server_loc)", " ")
foreach ($file in $files) {
IF ($file.length -eq 0) {
[string]$string = $file
$server_complete_Array = $server_complete_Array + "$string is empty."
}
elseif ($file.LastWriteTime -lt $yesterday) {
[string]$string = $file
$server_complete_Array = $server_complete_Array + "$string is old."
}
else {
[string]$string = $file
$server_complete_Array = $server_complete_Array + "$string is okay."}
}
Thank you for your help in advance. :)
Bit of a hack, but it should work with what you already have:
$file_array = "XXX_backup*.trn", "XXX_backup*.bak", "XXY_backup*.trn", "XXY_backup*.bak"
$FileNames = $server_files | select -ExpandProperty Name
$FileCheck = ('$Filenames ' + ($file_array | foreach {"-notlike '$_'"}) -join ' ')
Since you're already using wildcards in your file specs, this works with that by creating a command which filters the file name array through a series of -notlike operators, one for each file spec.
$FileCheck
$Filenames -notlike 'XXX_backup*.trn' -notlike 'XXX_backup*.bak' -notlike 'XXY_backup*.trn' -notlike 'XXY_backup*.bak'
Each one will filter out the names that match the file spec and pass the rest onto the next -notlike operator. Whatever falls out the end didn't match any of them
At this point $FileCheck is just a string, and you'll need to use Invoke-Expression to run it.
$ExtraFiles = Invoke-Expression $FileCheck
and then $ExtraFiles should contain the names of all the files that did not match any of the file specs in $file_array. Since the filters are being created from $file_array, there's no other maintenance to do if you add or remove filespecs from the array.