I came across this old thread, and none of the answers quite did what I wanted, and I overlooked Sverre's for some reason. I put this together since I needed to clean up a lot of horrible inconsistent lower-env SQL server database file placement and consolidate disk/folder structures. Hope this helps someone.
-- create a script that generates necessary commands to relocate a set of databases from
-- one location to another and some other stuff
-- 1) offline database
-- 2) alter databases modify files
-- 3) generates OS file move commands
-- 3.5) includes renaming the files to match database AND file type/enumeration
-- 4) online databaases databases using create database for attach commands
--== declare some configuration variavbles ==--
DECLARE @newpath SYSNAME = 'G:\DefaultInstance\QA'
DECLARE @databasewildcard SYSNAME = '%_NEW'
--== start with a commands table ==--
IF OBJECT_ID('tempdb..#commands') IS NOT NULL DROP TABLE #commands
CREATE TABLE #commands ([id] INT IDENTITY, [command] VARCHAR(MAX))
IF OBJECT_ID('tempdb..#excludedatabases') IS NOT NULL DROP TABLE #excludedatabases
CREATE TABLE #excludedatabases ([id] INT IDENTITY, [database] SYSNAME)
INSERT INTO #excludedatabases ([database]) VALUES ('exceptiondatabase_NEW')
--== insert commands : offline databases ==--
INSERT INTO #commands ([command])
SELECT
--'EXEC master.dbo.sp_detach_db @dbname = N' + QUOTENAME(sd.[name],'''')+ ';'
'USE [master]; ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' SET OFFLINE WITH ROLLBACK IMMEDIATE;'
FROM sys.databases sd
WHERE sd.[name] LIKE @databasewildcard
AND sd.[name] NOT IN (SELECT
ed.[database]
FROM #excludedatabases ed)
--== insert commands : modify logical physical file name ==--
INSERT INTO #commands ([command])
SELECT
'ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' MODIFY FILE (NAME = N' + QUOTENAME(smf.[name],'''') + ', FILENAME = N' + QUOTENAME(@newpath + '\'
+ CASE
WHEN smf.[type_desc] = 'ROWS' THEN sd.[name] + '_data_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.mdf'
WHEN smf.[type_desc] = 'LOG' THEN sd.[name] + '_log_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.ldf'
END
,'''') + ');'
FROM sys.master_files smf
JOIN sys.databases sd ON smf.[database_id] = sd.[database_id]
WHERE sd.[name] LIKE @databasewildcard
AND sd.[name] NOT IN (SELECT
ed.[database]
FROM #excludedatabases ed)
--== insert commands : generate OS move commands ==--
INSERT INTO #commands ([command]) VALUES ('/* --===== comment out these commands as a block - execute in file system =====--')
INSERT INTO #commands ([command])
SELECT
--'MOVE ' + QUOTENAME(smf.[physical_name],'"') + ' ' + QUOTENAME(@newpath + '\','"') AS [command]
'MOVE ' + QUOTENAME(smf.[physical_name],'"') + ' ' + QUOTENAME(@newpath + '\'
+ CASE
WHEN smf.[type_desc] = 'ROWS' THEN sd.[name] + '_data_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.mdf'
WHEN smf.[type_desc] = 'LOG' THEN sd.[name] + '_log_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.ldf'
END
,'"') AS [command]
FROM sys.master_files smf
JOIN sys.databases sd ON smf.[database_id] = sd.[database_id]
WHERE sd.[name] LIKE @databasewildcard
AND sd.[name] NOT IN (SELECT
ed.[database]
FROM #excludedatabases ed)
INSERT INTO #commands ([command]) VALUES ('*/ --===== comment out these commands as a block - execute in file system =====--')
--== insert commands : online databases ==--
INSERT INTO #commands ([command])
SELECT
'USE [master]; ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' SET ONLINE WITH ROLLBACK IMMEDIATE;'
FROM sys.databases sd
WHERE sd.[name] LIKE @databasewildcard
AND sd.[name] NOT IN (SELECT
ed.[database]
FROM #excludedatabases ed)
SELECT * FROM #commands
Please note that the same CAVEAT applies, I didn't use XP_CMDSHELL because permissions on our filesystems are NUTS!! So, I just generated MOVE commands to be run as a step to this work. Sadly a requirement for my environment until we get a handle on this inherited environment.