File and directory management is a pretty common administrative task. Here are some ways to accomplish typical tasks using PowerShell.
In Chapter 9 we discussed how you can use console redirection to send output to a text file and how to using Out-File, so we won't go into detail. Instead, we'll provide a few examples for creating text files. First let's look at a line of code that redirects output to a text file:
PS C:\> get-wmiobject win32_share > myshares.txt
Remember > send output to the specified file, overwriting an existing file with the same name. Use >> to append to an existing file. The file will be created if it doesn't already exist.
Here's the same expression, but using the Out-File cmdlet:
PS C:\> get-wmiobject win32_share | out-file myshares.txt -noclobber
We used -NoClobber to prevent Out-File from overwriting myshares.txt, if it already exists.
Finally, you can also use New-Item to create a file and even add some content to it:
PS C:\> $now=get-date PS C:\> new-item audit.log -type File -value "Created $now" -force Directory: Microsoft.PowerShell.Core\FileSystem::C:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 6/16/2006 10:40 AM 30 audit.log PS C:\> get-content audit.log Created 6/16/2006 10:40:30 AM PS C:\>
In this example we use New-Item to create a file called audit.log and give it some content.
Reading text files is pretty straightforward with Get-Content:
PS C:\> get-content boot.ini [boot loader] timeout=15 default=multi(0)disk(0)rdisk(0)partition(2)\WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Microsoft Windows XP Professional" /fastdetect /NoExecute=OptIn multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" / noexecute=optout /fastdetect C:\CMDCONS\BOOTSECT.DAT="Microsoft Windows Recovery Console" /cmdcons PS C:\>
We can use -TotalCount to display a specified number of lines from a text file:
PS C:\> get-content ADOXEXCEPTION.LOG -TotalCount 5 CADOXCatalog Error Code = 80040e4d Code meaning = IDispatch error #3149 Source = Microsoft OLE DB Provider for SQL Server Description = Login failed for user 'scriptaccess'. PS C:\>
In this example, the first five lines of a log file are displayed. You can get the same result with this expression:
PS C:\> get-content ADOXEXCEPTION.LOG |select-object -first 5
An advantage to this approach is that Select-Object also has a -Last parameter that you can use to display a specified number of lines from the end of the file.
Copying files in PowerShell is not much different than copying files in Cmd.exe. In fact, by default PowerShell uses the alias Copy for the Copy-Item cmdlet.
PS C:\> copy *.ps1 c:\temp PS C:\> get-childitem c:\temp Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 5/17/2006 8:31 PM 863 brace.ps1 -a--- 5/29/2006 12:18 PM 15 demo.txt -a--- 2/11/2006 6:26 PM 19817 ex060211.log -a--- 5/29/2006 10:58 AM 16 j.txt -a--- 5/23/2006 1:03 PM 811 out-grid.ps1 -a--- 5/23/2006 1:06 PM 1330 out-propertyGrid.ps1 -a--- 5/19/2006 11:17 AM 710 showprocessinfo.ps1 -a--- 5/19/2006 11:04 AM 477 showservices.ps1 -a--- 5/1/2006 8:10 PM 88 test.ps1 PS C:\>
This example copies all ps1 files in C: to C:\temp. You can also recurse and force files to be overwritten:
PS C:\>copy-item C:\Logs C:\Backup -recurse -force
This expression copies all files and subdirectories from C:\Logs to C:\Backup, overwriting any existing files and directories with the same name.
As you work with PowerShell, you'll discover that not every command you can execute in Cmd.exe is valid in PowerShell. For example, the following is a valid command in Cmd.exe:
C:\logs> copy *.log *.old
When you try this in PowerShell, you'll get a message about an invalid character. It appears the Copy-Item cmdlet works fine when copying between directories, but it can't handle a wildcard copy within the same directory.
Here's a work around:
BulkCopy.ps1
#BulkCopy.ps1 Set-Location "C:\Logs" $files=Get-ChildItem |where {$_.extension -eq ".log"} foreach ($file in $files) { $filename=($file.FullName).ToString() $arr=@($filename.split(".")) $newname=$arr[0]+".old" Write-Host "copying "$file.Fullname "to"$newname copy $file.fullname $newname -force }
With this script we first define a variable that contains all the files we want to copy by extension. Next we iterate through the variable using ForEach. Within the loop we break apart the filename using Split so we can get everything to the left of the period. We need this name so we can define what the new filename will be with the new extension, including the path. Then it's a matter of calling Copy-Item. Notice that in the script we're using the copy alias.
Provider Alert
If you look through the Help for Copy-Item and some of the other Item cmdlets, you will see a -Credential parameter. This might lead you to believe that you could use the -Credential parameter to copy files to a network and share and specify alternate credentials. Unfortunately, in the first version of PowerShell, the filesystem and registry providers do not support this parameter. Hopefully this will change in later versions of PowerShell. In the meantime, start a PowerShell session using the RunAs command if you need to specify alternate credentials.
The Remove-Item cmdlet has aliases of del and erase, and functions essentially the same as these commands in Cmd.exe:
PS C:\> remove-item c:\temp\*.txt
The cmdlet comes in handy when you want to recurse through a directory structure or exclude certain files.
PS C:\> remove-item c:\backup\*.* - recurse -exclude 2006*.log
This expression will recurse through c:\backup deleting all files except those that match the pattern 2006*.log. Like Copy-Item, you can also use -Include and -Credential.
Renaming files is also very straightforward:
PS C:\Temp> rename-item foo.txt bar.txt
This cmdlet has aliases of rni and ren, and like the other item cmdlets, it lets you specify credentials and force an overwrite of an existing file. You have to be a little more creative if you need to rename multiple files:
BulkRename.ps1
#BulkRename.ps1 Set-Location "C:\Logs" $files=get-childitem -recurse |where {$_.extension -eq ".Log"} foreach ($file in $files) { $filename=($file.name).ToString() $arr=@($filename.split(".")) $newname=$arr[0]+".old" Write-Host "renaming"$file.Fullname "to"$newname ren $file.fullname $newname -force }
This is a legitimate command in Cmd.exe:
C:\ ren *.log *.old
Since this doesn't work in PowerShell, we use something like the BulkRename script instead. This is a variation on our BulkCopy script from above. Instead of copy, we call ren. By the way, as the script is written above, it will recurse through subdirectories starting in C:\Logs, renaming every file it finds that ends in .log to .old.
Creating directories in PowerShell is nearly the same as it is in Cmd.exe:
PS C:\Temp> mkdir "NewFiles"
Alternatively, you can use the New-Item cmdlet that offers a few more features:
PS C:\Temp> new-item -type directory \\File01\public\sapien
The cmdlet also creates nested directories. In other words, like mkdir in Cmd.exe, it creates any intermediate directories:
PS C:\Temp> new-item -type directory c:\temp\1\2\3 Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp\1\2 Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 6/16/2006 1:56 PM 3 PS C:\Temp> tree /a Folder PATH listing for volume Server2003 Volume serial number is 0006EEA4 34AB:AD37 C:. +---1 | \---2 | \---3 +---jdh +---sapien \---temp2 PS C:\Temp>
Even though you can use dir in PowerShell to list directories and files, it is really an alias for Get-ChildItem. However, you can specify files to include or exclude in the search and also recurse through subdirectories:
PS C:\> dir -exclude *.old -recurse
Remember, even though PowerShell is displaying text, it is really working with objects. This means you can get creative in how you display information:
PS C:\Temp> dir -exclude *.old,*.bak,*.tmp -recurse | select-object ` >>FullName,Length,LastWriteTime | format-table -auto >> FullName Length LastWriteTime -------- ------ ------------- C:\Temp\1 6/16/2006 1:56:45 PM C:\Temp\1\2 6/16/2006 1:56:45 PM C:\Temp\1\2\3 6/16/2006 1:56:45 PM C:\Temp\jdh 6/16/2006 1:09:41 PM C:\Temp\sapien 6/16/2006 1:05:41 PM C:\Temp\temp2 6/16/2006 12:48:44 PM C:\Temp\temp2\bar.Log 11 6/16/2006 11:09:27 AM C:\Temp\showservices.ps1 477 5/19/2006 11:04:29 AM C:\Temp\test.abc 88 5/1/2006 8:10:10 PM C:\Temp\test.Log 88 5/1/2006 8:10:10 PM C:\Temp\test.ps1 88 5/1/2006 8:10:10 PM PS C:\Temp>
This expression recurses from the starting directory, listing all files that don't end in .old, .bak, or .tmp. Using the dir alias, the output from Get-Childitem is piped to Select-Object because we want to display only certain information formatted as a table.
Deleting a directory is essentially the same as deleting a file. You can use the rmdir alias for Remove-Item:
PS C:\Temp> rmdir sapien
PowerShell gives you a warning if you attempt to remove a directory that isn't empty:
PS C:\Temp> rmdir files Confirm The item at C:\Temp\files has children and the -recurse parameter was not specified. If you continue, all children will be removed with the item. Are you sure you want to continue? [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):n PS C:\Temp>
As you can see, the solution is to use the -Recurse parameter:
PS C:\Temp> rmdir files -recurse PS C:\Temp>