I'm trying to run a command to gather use profiles with certain requirements, here's my code:
#$numberOfDays = 30
#$numberOfDays
$profileStructsToRemove = Get-CimInstance Win32_UserProfile |
Where-Object {$_.LastUseTime -lt $(Get-Date).Date.AddDays(-$numberOfDays) } |
Where-Object {$_.LocalPath.ToUpper() -ne 'C:\USERS\ADMINISTRATOR'} |
Where-Object {$_.LocalPath.ToUpper() -ne 'C:\USERS\SOME_PROFILE_TO_KEEP'} |
Where-Object {$_.LocalPath.ToUpper() -ne 'C:\USERS\PUBLIC'}
$profileStructsToRemove #print
I use the $numberOfDays variable to determine the number of days subtracted from today's date that I want to use as the filter. Right now it's commented out, and the command succeeds, although since $numberOfDays isn't defined I assume it's using a null value? I'm not really sure but it works that way...
However, when I assign $numberOfDays to 30, it fails to populate the variable $profileStructsToRemove with ANYTHING at all. It just utterly fails. I could really use some input on why this is happenening.
How is the command working when $numberOfDays isn't defined? Is it just a null value, or treating it as 0 for the AddDays function?
Why is this command failing once $numberOfDays is assigned a value?
gms0ulman's helpful answer answers question #1 well (converting $null to an [int] yields 0).
As for question #2:
At least on Windows 10 on a non-domain machine, the .LastUseTime property seemingly always returns the current date and time, which (a) makes it useless and (b) explains why you didn't see any results.
You could try to check the .LastDownloadTime instead, if that field has a value in your case - I presume it would only have a value if the profiles are roaming profiles.
For reference, here's the full list of available time stamps:
LastAttemptedProfileDownloadTime, LastAttemptedProfileUploadTime, LastBackgroundRegistryUploadTime, LastDownloadTime, LastUploadTime, LastUseTime.
As for how to optimize the code in your question in general:
PowerShell string operators are case-insensitive by default, so there's no need for .toUpper().
You could combine your multiple Where-Object calls into a single one, and you can use
-notin with an array of paths on the RHS, instead of using -ne with individual paths.
To put it all together (PSv3+; keeping in mind that comparing against .LastUsedTime may be pointless):
$profileStructsToRemove = Get-CimInstance win32_userprofile | Where-Object {
$_.LastUseTime -lt $(Get-Date).Date.AddDays(-$numberOfDays) -and
$_.LocalPath -notin 'C:\USERS\ADMINISTRATOR',
'C:\USERS\SOME_PROFILE_TO_KEEP',
'C:\USERS\PUBLIC'
}
Yes, it's null, which added zero days. This is fine - you can test with:
$(Get-Date).Date.AddDays($null)
Are you sure there are profiles that match that data? Check the data when $numberOfDays is null to confirm.
Related
I am trying to extract each line from a CSV that has over 1million (1,000,000) lines, where the first character is a 1.
The 1 in this case, refers to the 1st line of a log. There are several different logs in this file, and I need the first line from all of them. Problem is (as you could understand) 1 is not unique, and can appear in any of the 12 'columns' of data I have in this CSV
Essentially, I would like to extract them all to a new CSV file as well, for further break down.
I know it sounds simple enough, but I cannot seem to get the information I need.
I have searched StackOverflow, Microsoft, Google and my own Tech Team.
PS: Get-Content 'C:\Users\myfiles\Desktop\massivelogs.csv' | Select-String "1" | Out-File "extractedlogs.csv"
The immediate answer is that you must use Select-String '^1 in order to restrict matching to the start (^) of each input line.
However, a much faster solution is to use the switch statement with the -File` option:
$inFile = 'C:\Users\myfiles\Desktop\massivelogs.csv'
$outFile = 'extractedlogs.csv'
& { switch -File $inFile -Wildcard { '1*' { $_ } } } | Set-Content $outFile
Note, however, that the output file won't be a true CSV file, because it will lack a header row.
Also, note that Set-Content applies an edition-specific default character encoding (the active ANSI code page in Windows PowerShell, BOM-less UTF-8 in PowerShell Core); use -Encoding as needed.
Using -Wildcard with a wildcard pattern (1*) speeds things up slightly, compared to -Regex with ^1.
I'm returning to powershell from bash after a long time, and I've found the where object behavior to be quite confusing.
Why does the following snippet return success? Nothing is found! Why does this not return failure like a grep would?
C:> Get-Process | ?{$_.name -like "laksdjfajsdfkjasdkf"}
C:> echo $?
True
tl;dr
# Run the command and, in addition to outputting to the console,
# collect the results in variable $result, via common parameter -OutVariable / -ov
# If you do NOT need to output to the console, simply use:
# $result = Get-Process | ...
Get-Process | ? { $_.name -like "laksdjfajsdfkjasdkf" } -ov result
# Test if the result is empty (using implicit Boolean conversion)
if (-not $result) { Write-Warning "Nothing matched." }
PowerShell's automatic (Boolean) $? variable in PowerShell is not the (abstract) equivalent of exit codes in traditional shells, as PetSerAl points out.
$? just tells you whether the last statement succeeded and the rules surrounding it are complicated, as the GitHub discussion that Owain Esau links to shows.
Succeeded means that no errors occurred, and a filtering operation not returning anything is success by that definition.
In short: $? is of limited usefulness in PowerShell.
However, the exit code of the most recently executed external program is reflected in automatic variable $LASTEXITCODE, so had you actually invoked grep, its exit code would be reflected there.
(And while $? is set immediately after execution of an external program to reflect $True if the exit code was 0 and $False otherwise, $? may already reflect something else by the time the statement finishes, depending on the specifics of the statement, such as enclosing the call in (...))
In the case at hand you're looking to determine whether the filtering operation performed by the call to the Where-Object cmdlet (invoked via its built-in alias ?) returned any matches, but in PowerShell that status is not reflected anywhere separately.
Therefore, you must examine the output itself to determine whether anything matched, as shown in the snippet at the top.
There are no errors in this scenario, but for the sake of completeness:
PowerShell's error handling is sophisticated, but complex, and again unlike that of traditional shells; you can find an overview here.
Just wondering if you can help me out.. I am trying to compare two list(txt file) and find strings that are in list A and not in List B and output it to another txt file.. anybody know how to do it using powershell ?
Here is what I have so far:
Compare-Object -ReferenceObject $FolderLists -DifferenceObject $AdUserName -passThru
I would like to find all strings that are in $FolderLists and not $AdUserName and possibly output it to another variable. The issue I am having is that it outputs strings that are not in both lists.
I assume $FolderList and $AdUserName are arrays of strings? You don't really need Compare-Object to compare arrays. It's as simple as this:
$FolderList | ?{$AdUserName -notcontains $_}
Compare-Object is for comparing the specified properties of collections of objects with common properties. You could do this with Compare-Object if you really want, like this:
Compare-Object $FolderList $AdUserName | ?{$_.SideIndicator -eq '<='} | Select-Object -ExpandProperty InputObject
But as you can see, it's overkill for this task.
To output the result to another variable, simply assign it:
$AnotherVariable = $FolderList | ?{$AdUserName -notcontains $_}
In a DOS script, if I have a variable string, how do I get a true or false that a certain string exists within that variable string? ( I don't want to have to create a temp file to accomplish this. I know how to do that hack already.) FIND.exe and FINDSTR.exe both seem to require a physical file rather than a variable.
I tried this, but it fails:
C:\Users\me>findstr.exe "Program" %ProgramData%
If you are trying to use a stock windows install - I don't think this can be accomplished as you describe using CMD.EXE as the closest you will get would be with the IF command, but it doesn't support a contains, it only contains the following operators:
EQU - equal
NEQ - not equal
LSS - less than
LEQ - less than or equal
GTR - greater than
GEQ - greater than or equal
If you can get away with what you want to do using those operators (and not strictly just a contains). You can also use the FOR command to split your string and test your matches on each individual element, though that seems rather brittle.
Since you have tagged this as PowerShell, I am going to go out on a limb and assume that a PowerShell script solution would be acceptable as well. If that is the case, then the solution is quite simple to demonstrate:
# create a variable $s with some string data
$s = "Some random program data"
# using String::Contains
if ($s.Contains("random")) { "Bingo" }
# using -like operator
if ($s -like "*random*") { "Bingo" }
# using the -match operator
if ($s -match "random") { "Bingo" }
Note: In PowerShell, the -contains operator is used to test for set membership, not substring matching. You will want to use the Contains method on a string object or the -like or -match operators to perform the desired patter matching.
Found another idea in the DOSTIPS forum:
set "str=-debug -verbose -normi -homedir -repo"
if "%str:-verbose=%" neq "%str%" (echo -verbose found) else (echo -verbose not found)
Instead of
C:\Users\me>findstr.exe "Program" %ProgramData%
try
echo %ProgramData% | findstr Program >nul
set RESULT=%ERRORLEVEL%
If it matches, %ERRORLEVEL% will be 0, otherwise, 1.
I am planning to write an object-oriented shell (based on Python). I have many ideas already. But before I am going to implement it, I want to inspire me by some existing shell.
What I basically mean by object-oriented:
Parameters are not just an array of strings but an array of objects.
The return value is also an object.
There is not just stdin, stdout and stderr but any possible number of named streams which can be of certain types (not just a stream of bytes).
I have read that the Windows PowerShell is somewhat like that (based on .Net). Though I am searching for some existing Linux/MacOSX shells.
Of course there is also IPython but it is not really intended as a Unix shell, i.e. piping stuff around is quite complicated.
Microsoft's Powershell. Installed by default on Windows 7 & Server 2008, can be installed on XP & Vista. It's a really good tool, a bit long to warm-up, but once it's done it's really usefull.
The features I really love in it is the filtering :
ls | where-object { $_.size -eq 0 }
who can be rewritten in the compact form
ls | ? { $_.size -eq 0 }
and the transformation (followed by it's compact form ):
ls | foreach-object { $_.name -replace "\folderName","daba" }
ls | % { $_.name -replace "\folderName","daba" }
you can also easily create pipe filter within the shell language, which is a pretty neat feature.
function concat()
{
Begin { $rez = ""; }
Process { $rez = $rez + $_ }
End { $rez }
}
ls | % { $_.name } | concat
The last expression list all files, extract the filename and concatenate them in a single string (it might be some cmdlet to do that but I don't remember the name).
Another important part of the powershell, is the introspection, you can query your object proprety/methods from the command line :
ls | get-member
Really useful to play with new objects, it's a bit more descriptive than dir()from python
Perhaps you may want to take a look at Pash.
It is an open source implementation of the PowerShell for other platforms. For educational purposes and inspiration it might be useful. Unfortunately, as far as I can see, this promising project is not being developed.
According to the shell comparison list on Wikipedia, the only existing shells which can do that are MS PowerShell and IPython (if that counts as a command shell) with the IPipe extension for piping.
If you only count real cross platform solutions, MS PowerShell cannot be used. There is the Pash port of it (thanks Roman to notice it), though it is incomplete and thus not really useable.
So, to answer my question: There is no such thing yet.